Description
Observable has been at stage 1 in the TC-39 for over a year now. Under the circumstances we are considering standardizing Observable in the WHATWG. We believe that standardizing Observable in the WHATWG may have the following advantages:
- Get Observable to web developers more quickly.
- Allow for a more full-featured proposal that will address more developer pain points.
- Address concerns raised in the TC-39 that there has not been sufficient consultation with implementers.
The goal of this thread is to gauge implementer interest in Observable. Observable can offer the following benefits to web developers:
- First-class objects representing composable repeated events, similar to how promises represent one-time events
- Ergonomic unsubscription that plays well with AbortSignal/AbortController
- Good integration with promises and async/await
Integrating Observable into the DOM
We propose that the "on" method on EventTarget should return an Observable.
partial interface EventTarget {
Observable on(DOMString type, optional AddEventListenerOptions options);
};
[Constructor(/* details elided */)]
interface Observable {
AbortController subscribe(Function next, optional Function complete, optional Function error);
AbortController subscribe(Observer observer); // TODO this overload is not quite valid
Promise<void> forEach(Function callback, optional AbortSignal signal);
Observable takeUntil(Observable stopNotifier);
Promise<any> first(optional AbortSignal signal);
Observable filter(Function callback);
Observable map(Function callback);
// rest of Array methods
// - Observable-returning: filter, map, slice?
// - Promise-returning: every, find, findIndex?, includes, indexOf?, some, reduce
};
dictionary Observer { Function next; Function error; Function complete; };
The on
method becomes a "better addEventListener", in that it returns an Observable, which has a few benefits:
// filtering and mapping:
element.on("click").
filter(e => e.target.matches(".foo")).
map(e => ({x: e.clientX, y: e.clientY })).
subscribe(handleClickAtPoint);
// conversion to promises for one-time events
document.on("DOMContentLoaded").first().then(e => …);
// ergonomic unsubscription via AbortControllers
const controller = element.on("input").subscribe(e => …);
controller.abort();
// or automatic/declarative unsubscription via the takeUntil method:
element.on("mousemove").
takeUntil(document.on("mouseup")).
subscribe(etc => …);
// since reduce and other terminators return promises, they also play
// well with async functions:
await element.on("mousemove").
takeUntil(element.on("mouseup")).
reduce((e, soFar) => …);
We were hoping to get a sense from the whatwg/dom community: what do you think of this? We have interest from Chrome; are other browsers interested?
If there's interest, we're happy to work on fleshing this out into a fuller proposal. What would be the next steps for that?