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

Jason Orendorff jason.orendorff at gmail.com
Wed Apr 17 17:50:22 PDT 2013


On Tue, Apr 16, 2013 at 4:28 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:
> 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.

I agree.

I have a ton of minor feedback and questions. Sorry it's so long.
There's more stuff I'd like to reply to but don't have time.

> interface EventStreamResolver {

I get the parallel with FutureResolver but this name doesn't work for
me. "FutureResolver" makes sense because the whole point of a Future
is to be resolved eventually. EventStreams aren't like that: many
(most?) want to keep pushing events indefinitely, and only ever become
"resolved" by freak accident.

>   void continueWith(optional any value);

I'm having trouble following the parallel with
FutureResolver.resolve() here. What's this for?

This seems like a mixing of layers to me. Here's how I interpret this
whole design:

- Event producers implement an extremely simple interface consisting
of a single subscribe() function (the StreamInit callback) that
interacts with a small listener interface (EventStreamResolver).

- EventStream is a concrete class that builds a dazzling array of
useful high-level operations on top of the aforementioned low-level
protocol. EventStream is what event consumers use in practice.

So—again this is all just how I see it right now—there are two nicely
independent things going on: a minimal low-level protocol, and a
high-level convenience class. "Obviously" you wouldn't have
EventStreams as part of the low-level protocol. Hence my confusion
about continueWith.

Separately: it seems like if you're an event producer, and you want to
stop sending events to a resolver and have some other event producer
send it events instead, you shouldn't have to tell the resolver
"please subscribe yourself to that stream over there". You can just
subscribe it. Instead of:
    resolver.continueWith(otherStream);
just write:
    otherStreamInit(resolver);

Then again, you could say exactly the same things about
FutureResolver.resolve(), and I don't understand its purpose either.
Probably just me.

> 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.

Mmmm. The interesting thing about Futures is... well, I'll just link to
  http://domenic.me/2012/10/14/youre-missing-the-point-of-promises/

Not that I really think you're missing the point— but .listen() is
only a sink. The combinators in your blog post are the exciting new
ability on offer here. EventStreams are cool because they compose.

> 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.

Rhetorical nit: This explanation makes it seem more complicated than
it is. The way to explain and justify the design is to show simple
examples. Bacon's readme does this well. You only have to read the
first 5 lines of code here to see what Bacon is about:

  https://github.com/raimohanska/bacon.js/blob/master/README.md#intro

> complete/reject/continueWith all kill the resolver, so that none of
> the methods work afterwards (maybe they throw?).

That seems sensible. OTOH FutureResolver seems to make all those
methods no-ops instead.  (Step 1 of each method's implementation: "If
the context object's resolved flag is set, terminate these steps.")
I'm not sure what that's about. Maybe worth asking.

> * 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()).

I can imagine that being true, but concrete example use cases would
help. It is easier to think of cases where buffering doesn't matter or
where you really don't want buffering (e.g. because it chews up a lot
of memory that you can never free).

> All streams need some way of unlistening.  Suggestions welcome as to
> how best to do this.

Bacon offers two equivalent ways of unsubscribing.

1. Bacon's equivalent of the StreamInit callback returns an
unsubscribe function. Each subscriber therefore gets its very own
unsubscribe callback.

2. Additionally, Bacon's equivalent of the EventStreamResolver.push()
method can return a special value (Bacon.noMore) that means
"unsubscribe me".

-j


More information about the es-discuss mailing list