Okay, I'm going to throw my hat back in and see if I can resurrect this a little.
What I'm going to propose is slightly different than the current proposal and different than RxJS, but I strongly feel it will work.
APIinterface Observable<T> { new ( initialization: ( nextHandler: (value: T) => void, errorHandler: (err: any) => void, completeHandler: () => void, signal: AbortSignal ) => void; ): Observable<T> subscribe( nextHandler?: (value: T) => void, errorHandler?: (err: any) => void, completeHandler?: () => void, signal?: AbortSignal ): void; forEach(nextHandler: (value: T) => void, signal: AbortSignal): Promise<void> first(signal: AbortSignal): Promise<T>; last(signal: AbortSignal): Promise<T>; }
The idea is to remove the need to define Observer
and Subscriber
, as in other Observable implementations, and use simple functions instead. Also using a cancellation token (ala AbortController/AbortSignal) instead of introducing a Subscription
type.
I realize that AbortController
and AbortSignal
are not a part of JavaScript proper. However, I strongly feel JavaScript could use a cancellation primitive, and Observable, which is also a primitive, is not as useful without it.
Below is the simplest use case for an observable. A synchronous set of values.
test("should at least work", () => { const source = new Observable((next, error, complete, signal) => { next(1); next(2); next(3); complete(); }); let results = []; source.subscribe(value => results.push(value), null, () => results.push("done") ); expect(results).toEqual([1, 2, 3, "done"]); });Handling of "firehose" synchronous data
With a cancellation token, like AbortSignal
, handling synchronous firehoses and stopping them due to external unsubscription becomes a bit more intuitive than it was with previous designs, IMO:
test("should handle firehose", () => { let loops = 0; const source = new Observable((next, err, complete, signal) => { for (let i = 0; i < 1000000000 && !signal.aborted; i++) { next(i); loops++; } // this will noop due to protections after abort below // which is "unsubscription". complete(); }); const controller = new AbortController(); const results = []; source.subscribe( value => { results.push(value); if (results.length === 3) { // "unsubscribe" controller.abort(); } }, null, // complete should not be called, because of the // abort (unsubscription) above () => results.push("done"), controller.signal ); expect(loops).toBe(3); expect(results).toEqual([0, 1, 2]); });Concessions
first
and last
may not be necessary, and are more "nice to have"s for this type. Their primary use cases would be for wrapped HTTP calls, which, in a world where AbortSignals were prolific, should probably just be done via fetch
.
There are a few cons to this design. Notably, from my perspective, it's not completely compatible with current popular designs. But I'm less worried about that than getting the appropriate primitives into the language.
Other thoughtsIt's possible to have this implement Symbol.asyncIterator
with a known behavior, like buffering all values internally until they are read. This, of course, comes with some potential issues around back-pressure and memory pressure, but I think that's easy to understand for most people who might use this type with for await
.
Another con is creating a "Subject", which is a common type created to compose with observables, becomes mildly challenging, in that it would need to be something that could be destructured into three functions and an abort signal, but again, I don't think that's really a concern for language authors. The community can take care of that.
LinksI've tossed together a demo here.
Repo: https://github.com/benlesh/tc39-observable-proposal
Codesandbox: https://codesandbox.io/s/tc39-observable-proposal-proposed-change-uxh4p
slikts, rgbkrk, SerkanSipahi, fxck, johnlindquist and 47 moreshobhitg, SerkanSipahi, AdrienRedon, trotyl, alex-okrushko and 5 morefejes713, topaxi, seokju-na, SerkanSipahi, Itrulia and 18 morebrandonroberts, EstenGrove, SerkanSipahi, irustm, AdrienRedon and 7 morebrandonroberts, jcampuza, anbalase, SerkanSipahi, lxsmnsyc and 2 more
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4