DOM EventStreams (take two on Streams): Request for feedback

Domenic Denicola domenic at
Thu Apr 18 17:11:27 PDT 2013

One bigger question: what is the DOM use case for event streams?

That is, it's very clear what the DOM use cases are for binary data streams. (Most urgently, streaming XHR, but also perhaps unifying the many interfaces that use object URLs as a means of connecting separate streams of data; also exposing the browser's GZIP capabilities; and so on [1].)

But for event streams it's less clear what urgent problem they solve. The example you've shown so far is basically just a different way of doing Object.observe, with some nice sugar and of course those combinators. But the basic capabilities of the platform are not expanded, and sugar seems like a library-level concern. Nevertheless, there's many allusions to DOM use cases in your blog posts, so a listing of those would be helpful.

In other words: if there are many use cases for the DOM where event streams make sense, great! In the spirit of standardizing promises, it's good to standardize a common idiom so we don't do things in many different ways across the DOM APIs. But if the only use case is just to notify of property changes, Object.observe handles that nicely without streams. What else needs event streams?


> -----Original Message-----
> From: es-discuss-bounces at [mailto:es-discuss-
> bounces at] On Behalf Of Tab Atkins Jr.
> Sent: Tuesday, April 16, 2013 19:29
> To: es-discuss
> Subject: DOM EventStreams (take two on Streams): Request for feedback
> My first attempt at feedback for my Stream proposal was unfortunately
> bogged down with a lot of confusion over terminology and meaning.  I'd like
> to start fresh and hopefully head off a lot of confusion early-on, so here's
> take two.
> Now that DOM has added Futures, I've started looking into converting
> various event-based APIs into being future-based.  This has been a great
> success, but there are some things that can't be turned into futures (because
> they update multiple times, or don't have a notion of "completing"), but
> have the same lack-of-need for the full DOM Event baggage.  I think these
> cases will be fairly common, and further, that a good solution to the problem
> for DOM will be pretty useful for general programming as well.
> This is explicitly *not* an attempt to solve the "binary"/"IO" stream use-case,
> as exemplified by Node Streams <>.
> While structurally similar, binary streams have a lot of unique features and
> pitfalls that make event streams a poor fit for them: they need to batch up
> data by default, they need to be able to apply backfill data, etc.  I think we
> *also* need to develop such an API, but it'll be separate from this one.  (I
> suspect it may look very similar, though, so it's good to keep that in mind
> when naming.)
> So, without further ado, here's the basic API for my proposal for
> EventStreams:
> ~~~~
> callback StreamInit = void (StreamResolver resolver); callback AnyCallback =
> any (optional any value); typedef (EventStream or Future or Iterable)
> StreamLike; typedef (string or number or boolean or AnyCallback)
> updateFilter;
> [Constructor(StreamInit init)]
> interface EventStream {
>   EventStream listen(optional AnyCallback? listenCB = null, optional
> AnyCallback? completeCB = null, optional AnyCallback? rejectCB = null);
>   Future complete(optional AnyCallback cb);
>   Future catch(optional AnyCallback cb);
>   Future next(optional updateFilter, optional anyCallback cb); }
> interface EventStreamResolver {
>   void push(optional any value);
>   void complete(optional any value);
>   void continueWith(optional any value);
>   void reject(optional any value);
> };
> ~~~~
> This API is intentionally very similar to that of Futures, because it's intended
> to solve similar problems, and I think the shape of the Futures API is pretty
> good.
> An EventStream represents a stream of events or values.  It's roughly
> equivalent to the concept of "signals" or "event streams" from functional
> reactive programming, or the concept of an "observable" or "task" from
> several functional async programming models.
> An EventStream pushes out 0 or more updates, then optionally completes or
> rejects.  The .listen() function is the basic way to respond to an event stream,
> allowing you to register callbacks for any of those three events.  It returns
> the same event stream back, for chaining.
> For convenience, event streams have several functions that let you listen to
> just a single event, returning a Future.  You can listen for the stream
> completing, rejecting, or for the next update (possibly filtered).  **Important
> note**: consuming an event stream using repeated .next() calls rather than
> a single .listen() call is lossy - multiple updates can happen between the tick
> that .next() is called and the tick that the future resolves, and the future will
> only contain the value of the first one.
> Like Futures, EventStreams separate the power to read/respond to an event
> stream and the power to update an event stream into two separate objects.
> The former is returned by the EventStream constructor, while the latter is
> passed into the constructor's callback argument.  The resolver's methods
> control the event stream's state - .push() puts an update on the stream,
> which'll be passed to all the listen callbacks,
> .complete() and .reject() end the stream with the passed value/reason,
> while .continueWith() delegates to another stream.
> complete/reject/continueWith all kill the resolver, so that none of the
> methods work afterwards (maybe they throw?).
> Additional Work
> ----------------
> In my blog post <>, I sketch out several
> event stream combinators, which showcase the true usefulness of this kind
> of abstraction, as you can manipulate and combine event streams
> *far* more easily, readably, and possibly performantly than the same tasks
> done with normal DOM Events or callbacks.
> Some degree of buffering seems desirable, both for DOM use-cases and
> general ones:
> * Several DOM use-cases really want to be able to remember the "current"
> value (from the most recent update) - this applies to all the "watch a value
> changing" APIs, like I suggest at the end of my blog post.  I think this can just
> be a subclass of EventStream, perhaps named UpdateStream, which
> automatically calls new listenerCBs with the current value before any
> updates, and which exposes a
> .value() function which is identical to .next(), but checks the current value
> first.
> * It seems that a bunch of manual use-cases would benefit from auto-
> buffering any updates until the first listener is attached (via
> .listen() or .next()).  How do we accommodate the choice?  Should this just
> be the default for manually-created event streams, with DOM use-cases
> defaulting to not buffering?
> * Right now, consuming streams piecemeal with .next() (rather than
> .listen()) is lossy.  Should we have some way to force full buffering, so that if
> you're consumign it piecemeal, it waits until the next
> .next() call to inform you of updates?
> The previous point is probably something we only want to expose for "single-
> listener" streams, which we should allow the creation of somehow.  A single-
> listener stream could default to full buffering.
> Enforcing single-listening is easy - if someone calls .listen(), it's sealed to
> future .listen() or .next() calls until you unlisten.  If it's not sealed, anyone can
> call .next() for the very next value, which means you can chain .next() calls
> safely.  Maybe calling .next() multiple times should return futures for
> *successive* values?  That sounds like it would match a lot of people's
> intuitions, and would match up well with using event streams for things like
> parsing streams
> - if you need the next two tokens from a token stream, just call
> .next() twice and use Future.all() to wait for them both to complete.
> Perhaps these single-listener streams could be called ValueStream, since
> they'll be for getting individual values asyncly.
> All streams need some way of unlistening.  Suggestions welcome as to how
> best to do this.  Maybe calling .listen() should actually return a new stream
> slaved to the original, and you can just call .unlisten() on it to destroy the
> listeners?  That avoids the "just pass the original callback" problem when you
> have anonymous callbacks.  That still means that "x.listen(); x.listen();
> x.unlisten();" would destroy
> *both* sets of callbacks, but I dunno how best to solve this.
> I think that's about it for now.  ^_^  Hopefully this time my intentions and
> goals are clearer, so we can start from a common slate rather than arguing
> about definitions and getting confused!
> ~TJ
> _______________________________________________
> es-discuss mailing list
> es-discuss at

More information about the es-discuss mailing list