Tab Completion

I'm Tab Atkins Jr, and I wear many hats. I work for Google on the Chrome browser as a Web Standards Hacker. I'm also a member of the CSS Working Group, and am either a member or contributor to several other working groups in the W3C. You can contact me here.
Listing of All Posts

Futures Eliminate Time

Last updated:

I was thinking a bit yesterday on Domenic's blog post "You're Missing the Point of Promises", and have some random thoughts about the real effect of Futures on code.

In short: Futures abstract away the concept of time from your code.

Our programming languages today simply aren't well-designed to handle time, where an operation can take a non-trivial amount of time to complete. If you don't want to just block until it finishes (effectively ignoring the time component), you have to go through all kinds of contortions to handle it properly.

In a good language like JS, the contortions aren't too terrible. You end up with callback hell, but if you've ever tried to do the same in languages like Java, you quickly realize how good we have it in JS.

Fundamentally, though, these contortions are just hacks. They're broken - the language doesn't actually cater toward them, and you lose a lot of functionality. One of the most important things you lose is the ability to respond to errors.

Functions Abstract over Space

Let's step back a moment, and consider this issue more abstractly.

What are functions? What do they, fundamentally, accomplish?

It's pretty simple (at least, if you're already in my head): functions abstract over space, allowing you to ignore where code is written in your source file. You can write code anywhere you want, and then "call" it, making it act as if it was written right where you want it. (It also lets you call that code multiple times, but that's a step beyond what I'm trying to do here, so I'll ignore it.)

Imagine, though, that you've been working in a language without any functions. This language has a lot of other modern features, like the ability to throw and catch errors, but it only recently added this newfangled "function" concept, and not everything is well-integrated yet. (Let's call it "Mauvascript".)

In particular, throwing and catching errors is based on source location. Writing code this is okay:

try {
  if( x == 0 )
    throw Error("Tried to divide by zero.");
  y = 1/x;
} catch(e) { ... }

This works exactly as expected - if x is zero, the error gets thrown, and caught by the try/catch block.

However, because functions are brand new, the following code doesn't work as expected:

function invert(x) {
  if( x == 0 )
    throw Error("Tried to divide by zero.");
  return 1/x;
}

try {
  y = invert(x);
} catch(e) { ... }

In a modern language, this transformation, moving the code around in the source file, doesn't have any confusing effects. The error will get thrown, propagate up the call chain, and be caught by the try/catch at the calling site.

But this isn't a modern language, it's Mauvascript. Functions are newfangled, and things like "errors should propagate up the call chain" are complicated and non-obvious. Instead, Mauvascript makes errors propagate up the lexical scope (that is, just looking at wider scopes in the source code) and makes uncaught errors leaving functions cause the function to return nothing.

You should be able to imagine the pain that people writing Mauvascript would have when using functions. If you use functions to start moving code around in space, suddenly errors stop working correctly! Dealing with any sort of error-handling becomes far more difficult.

You could imagine some conventions arising to deal with it, of course. A popular web server using Mauvascript, perhaps called Mode.ms, might develop the convention that every function has to return a 2-element array, with the first element holding the error object, if there was any, and the second holding the normal return value, if it completed successfully. Every function would need to wrap its entire contents in a try/catch, then, with the error return done in the catch. This is less convenient, but it's predictable, and that would allow a number of control-flow libraries to pop up to help make error-handling more convenient.

Fundamentally, though, all of this would just be papering over the base problem, which is that errors shouldn't be tied to the location of the code in the source file. Moving code around shouldn't have any effect on its behavior, which means that errors need to pay attention to the call stack, and propagate up to the calling site when uncaught within a function.

Futures Abstract over Time

Today's Javascript is in the exact same situation as our imaginary Mauvascript, except the new concept being dealt with is code moving around in time, rather than space. Long-running functions take callback arguments, which has the effect of shifting the block of code into the future to be called later (just like how functions shifted a block of code into another place in the source file).

The problems Mauvascript had with errors in functions are exactly like the problems we have today with errors in callbacks, because the language doesn't support async code natively.

Futures are the abstraction that fixes this. They let you schedule code to be run in the future, and if the code throws an error, you can catch it later. Nothing special needs to be done on your side, no special conventions need to be adhered to. Whenever you chain code to run after a future, you can set two callbacks, one for normal code and one for the error case. If you omit the error code, it'll just automatically skip your normal code and jump to the next code in the chain until it finds something that can handle it.

This is exactly equivalent to the behavior of proper functions and errors - the error bubbles up to the call site, and if you don't catch it, continues bubbling up, skipping over any code you write between the erroring call and the end of your function.

This is the true value of futures - not that they make it easier to write async code (callbacks and convention do that reasonably well already), but that they capture the notion of error flow properly across async boundaries.

An Even Better Future?

It seems likely to me that, now that Futures are finally gaining proper popularity (both in JS and in more traditional languages like C# and C++), some language will eventually pop up with more integrated syntax for async code. I'm not sure what it would look like, though. Does anyone know any examples of experimental languages that do something like this?

Explaining Futures

Last updated:

DOM Futures were only recently introduced, and it seems like suddenly every API is being forced into using them. Why? How did this crazy idea suddenly appear? Where was the discussion? Hopefully I can answer a few of these questions, and explain why Futures are so useful and should be used widely.

Future History

First of all, Futures didn't spring fully-formed from Anne van Kesteren's head. If you've been doing JS programming the last several years with any of the popular frameworks, you've probably already used Futures, though they might have been called "Promises", "Deferreds", or "Thenables".

Specifically, Futures are based on the Promises/A+ version, standardized by Domenic Denicola and others. Over the last several years, this has turned out to be the most popular and most technically worthy version of Promises.

About two years ago, Alex Russell gathered together several influential and super-smart people with experience in modern webdev, like Erik Arvidsson, Yehuda Katz, Jake Archibald, and others, and started iterating on a proposal to do Promises on the web. While doing so, he also kept some awesome old-hand programming language designers, like Mark Miller, in the loop so that obvious pitfalls and historical errors could be avoided. The final API ended up being Promises/A+ compatible, and had a very sharp, small API that still represented tons of power. This was then ported over into the DOM spec by Anne as DOM Futures.

In short, this isn't some half-baked idea put together in five minutes. It's based on years of real-world experience and lots of thought from really smart hackers.

Future Value

What exactly do Futures bring to the table? Why are they better than Events, or just callbacks?

There are lots of reasons to use some form of callback-based asynchronous API, but one popular reason is because you have some task that you don't want to block the main thread on (maybe it requires file or network IO, for example), and you want to be able to return its value when it finishes.

This sort of pattern shows up everywhere in DOM and related APIs. Unfortunately, it's implemented in a myriad of ways. Some APIs return a dummy object, and then fire a DOM Event at it when the operation is finished. Others fire a DOM Event at an appropriate global interface, with some information to figure out what thing the event was for. Others return a dummy object with a few callback-registering functions (similar to, but not using, DOM Events). Others take callbacks directly in the argument list. Others take callbacks in an options object.

And then, with all the above methods, APIs may or may not have some way to detect errors in the operation, which increases the complexity further. APIs may or may not have an easy way for multiple functions to be registered for the event. APIs may or may not have a way for code to get at the value of the operation after it completes.

All of this adds up to a metric fuckton of API surface for something that is a very simple, small set of meaningful API concepts.

The core value of Futures is that it unifies all of these into a single, idiomatic pattern. Whenever a function will kick off an async task, it should return a Future. The user can then register "accept" or "reject" callbacks on the Future, which get called when the operation completes successfully or fails with an error, respectively. You can register callbacks multiple times, and they'll all be called as appropriate. After the operation completes, you can still register callbacks on the future, and it'll just call them "immediately" (next tick) with the completion value or rejection reason, just like if they were registered before it completed.

"But Tab!", I hear you say, "you could just as easily return an EventTarget object and just fire events! That would accomplish the same thing, but without inventing something new and inconsistent with the rest of the platform!". (Or equivalently, we could standardize some callback argument pattern, or something else.)

You're right, we could! If this was all that Futures offered, they would be a much more difficult sell, and probably not worth the effort.

However, the fact that Futures capture this pattern in a first-class value means that we can push more power into the abstraction. Futures are fundamentally better than the existing patterns for at least 5 strong reasons.

Reason 1: Chaining

Most of the API patterns I rattled off earlier have no convenient way to chain operations. That is, you can't easily schedule a second function to run after your first callback finishes. Most of the time, you have to roll your own chaining somehow, like registering an anonymous function that wraps your two pieces of code.

Futures make this trivial - the return value of .then() (the callback registration function) is another Future, which completes when the callback is finished with the callback's return value. (Actually it's even better - see the next section.) This means you can chain a second function just by calling .then() on this returned value! It's even simpler than it sounds:

someAsyncFunc().then(cb1).then(cb2);

Tada!

Reason 2: More Chaining

There's another type of chaining that is prevalent in async code, which is deeply hated - NESTING HELL. This happens when the first callback finishes up by kicking off another async operation, so you have to pass in another callback to handle that. If you're coding with a lot of anonymous functions (which is totally reasonable, because you're basically just splitting up your literal code into little async chunks), these callbacks end up marching ever rightward, making it difficult to read and understand your program flow.

Futures make this sort of thing trivial, again. Remember in the last section, where I said that the future returned by .then() (let's call this Future2 - you'll see why) completed when the callback returned, with the callback's return value? That's true, but there's some additional magic involved - if the callback returns a future (Future3), then Future2 slaves itself to Future3's state. It waits to resolve until Future3 resolves, and then adopts the same state, accepting or rejecting with the same value. This means that you can do async chaining without the horrible leftward march!

asyncFunc().then(function(val) {
    return anotherAsyncFunc();
  }).then(function(val) {
    // only runs when anotherAsyncFunc()'s operation finishes!
    return yetAnotherAsyncFunc();
  }).then(function(val) {
    // only runs when yetAnotherAsyncFunc()'s operation finishes
  });

Reason 3: Linear callback growth

For those API variants that let you register both success and error callbacks, the lack of good chaining (see reasons 1 and 2) meant that the number of necessary callbacks would grow exponentially:

oldAsyncFunc(function(success) {
    return anotherAsync(function(success) {
        ...
      }, function(error) {
        ...
      });
  }, function(error) {
    return yetAnotherAsync(function(success) {
        ...
      }, function(error) {
        ...
      });
  });

That's 6 function declarations - 2 at the top level, 4 nested - and it would be 14 if the same pattern continued one more level. Of course, in practice by this point people are forced into using named functions, just to avoid all the pointless duplication.

In the reasonably common case where the error function is returning some appropriate default value of the same type as the success function, Futures simplify this, so that you don't have to repeat yourself. This means that using anonymous functions stays viable:

newAsyncFunc().then(function(success) {
    return anotherAsync();
  }, function(error) {
    return default;
  }).then(function(success) {
    return yetAnotherAsync();
  }, function(error) {
    return default2;
  });

This code has only four function declarations, 2 per level. If it went another level deep, it would only be 6. There's no duplication necessary at all.

Reason 4: Errors are easy to deal with

In normal async code, errors are the devil. Throwing errors and using callbacks simply do not mix, because where you going to put the try/catch? You can't put it around the function that takes the callbacks, because it successfully returns immediately. You can't put it around the callbacks, because they're not called until later. The only way to mix them is to invent your own complex wrapper system, and always use it.

Futures takes care of all this for you, in the same trivial way it does the rest. Recall from Reasons 1 and 2 that whenever you attach a callback to a future, it returns a brand new future that resolves to the callback's return value, to make chaining easier. Like all futures, this brand new future can take both accept and reject callbacks, and for good reason - if the callback throws, it'll get caught by the future and just trigger the reject callback automatically!

Reason 5: Future combinators

One of the most annoying things to handle when doing async code (besides chaining, more chaining, and errors) is synchronization. If you have a single async operation you want to run some code after, that's fine. If you have two async operations, and you want to wait until they both finish and call some function with both of their results, you're on your own. You have to manually roll some synchronization primitives, where you pass different dummy functions to both, which use some communication channel to tell each other when they're finished and what their return value was, so the last one to finish can finally call your function. If you have two async operations and you just want to respond to the first of them, you've got to do that stuff all over again, just with slightly different synchronization code.

Once again, futures makes this trivial. The Future interface has several static functions defined on it which combine futures together into new futures:

  • Future.all(): if all the passed futures accept, the output future accepts, with an array of their values. If any of them rejects, the output future rejects with its reason.
  • Future.some(): if any of the passed futures accepts, the output future accepts with its value. If they all reject, the output future rejects with an array of their reasons.
  • Future.any(): whichever of the passed futures is first to accept or reject, the output future does the same with the same value.

For example, if we assume there's a future-returning XHR API, and you want to make two separate XHRs and run some code when they both return, it's easy:

Future.all(getJSON(url1), getJSON(url2)).then(function(arr) {
    // do stuff with arr[0] and arr[1], the JSON results
  }, function(error) {
    // handle the error
  });

Doing the same with today's XHR code, or even jQuery's older (non-Deferred-based) XHR APIs, is non-trivial, but a fun exercise to try if you've never had to do it before. It stops being fun the dozenth time you have to rewrite it in production code, though.

There are potentially even more useful combining operations, but these three should cover the majority of cases.

Future Conclusion

Hopefully, this has enlightened you as to why DOM chose to add Futures, and why some of us are so excited and eager to use Futures everywhere in APIs.

In the DOM world and other closely-related APIs, we're not going to stop using futures, and the value of futures grows even more as more specs use them, due to network effects and developer interest. Please do the right thing, and use Futures when appropriate in your own APIs. Ask us (on www-dom@w3.org) if you're not sure whether or not your API should use Futures, or how best to employ them.

Font Load Tracking with Futures

Last updated:

This is an attempt to rewrite the CSS Font Load Events spec to instead use Futures, as I believe it makes for much simpler and easier-to-use interface.

API

partial interface document {
  readonly attribute FontList fonts;
}

enum LoadStatus { "loading", "ready" };

interface FontList {
  /* indexed getters for Font objects corresponding to 
     all the @font-face rules in the document */
  Future ready()
  boolean checkFont(DOMString font, optional DOMString text = " ");
  Future loadFont(DOMString font, optional DOMString text = " ");
  attribute LoadStatus loadStatus;
}

interface Font {
  Future ready()
  /* readonly versions of all the CSSFontFaceRule attributes,
     minus ones that point into the stylesheet */
}

Replacing document.fontLoader

First of all, I'm replacing document.fontLoader with document.fonts, an array-like of Font objects. Each Font object is a readonly version of a CSSFontFaceRule object, without any links into the CSSOM. This is necessary if we want to expose rules from cross-origin stylesheets because we're not allowed to expose cssText on them. It should also be something that can be safely exposed to Workers, as there's no DOM/CSSOM link to make it unsafe. The FontList contains Font objects for all the @font-face rules in all the stylesheets for the document, in document order.

Replacing notifyWhenFontsReady()

Next, let's replace notifyWhenFontsReady(). This is a textbook example of something that's already a Future, but doesn't know it yet.

To replace it, I added FontList#ready(). It must return a new future on every call. The state of the returned future varies over time:

  • Futures requested while the document is loading are in the "pending" state. They are accepted when the document has finished loading, there are no pending font loads, and no pending layout operations that may depend on the state of font loads.
  • After the document is finished loading, if there are no currently pending font loads or layout operations that may depend on the state of font loads, futures requested are already accepted.
  • Otherwise, futures requested are in the "pending" state, and they are accepted when there are no pending font loads or layout operations that may depend on the state of font loads.

(Is there a reasonable accept value to return here?)

Fixing loadFont()

The current loadFont() is another textbook "future but doesn't know it" API, with its success and error callbacks. To fix it, I just simplified its API to be the same as checkFont(), and had it return a Future.

If the requested fonts are already loaded, the returned future is already accepted. Otherwise, the function kicks off the necessary loads and returns a pending future, which is accepted when the fonts load, or rejected if any of the fonts fail. (Exactly equivalent to simply retrieving all the relevant Font objects, and doing Future.all(...fonts.map(x=>x.ready())).)

checkFont() is unchanged. I'm unsure what it's necessary for, but assuming that it's worth keeping, it works fine.

Replacing the loading/loading-done events

These are replaced with a simple attribute loadStatus. While the document is loading, or while fonts are loading, the status is "loading". Otherwise, the status is "ready".

This isn't quite an adequate replacement, but I'm planning to augment it with a Streams proposal that makes it trivial to get a stream of changes to an attribute, so you can then just watch the loadStatus attribute.

Replacing the loadstart/load/error events

This is another simple Future conversion. Instead of events for every font fired at a central FontLoader, we now have a convenient way to get at individual @font-faces via the FontList. So, this is replaced by a new Font#ready() function on individual Font objects.

The future returned is pending while the document is loading, before the font is needed, or while the font is loading. It's accepted after the font finishes loading, or rejected if the font encounters an error while loading.

(Should this have some value when you accept? Perhaps just the Font object itself, so you can reuse the same callback on lots of Fonts and be able to distinguish which one loaded?)

(What's the convention for platform rejections? Probably reject with the Error object?)

Once we iron out the design of ProgressFuture, this should change to a progress future, which reports its progress as "unloaded" or "loading" before completing.

That's It

I think the new API is much smaller and easier to use, and more functional than before. Once we get Streams for observing property changes, it'll be a strict superset of the old functionality, but without any race conditions or the unneeded bagged of DOM events.

DOM EventStreams

Last updated:

DOM Futures are a recently-added feature that captures a very common and useful callback pattern - a single task that will either complete in the future or has already completed, or possibly results in an error - in an easy-to-use and idiomatic way.

While trying to convert some upcoming DOM APIs to use Futures, I kept running into some particular patterns that could benefit from the Futures treatment (they don't need the baggage of DOM Events), but that don't fit the Futures model, because they resolve to multiple values or don't even "finish" in any meaningful way.

By looking at my DOM-related use-cases and similar APIs in other languages, I've come up with the following three classes I'd like to add to DOM/JS:

EventStream - represents a series of events as a first-class value, which means you can apply combinators to it, etc. Multi-listener, lossy by default. Should probably have a switch to auto-buffer results before the first listener is attached. I expect to define a generic way to create an EventStream from any element/event combo, which pushes the event object as the update value whenever the event reaches the element without being cancelled by an ancestor.

ReactiveValue - represents an underlying value, and updates every time the value changes. The "current value" (value of the most recent update) is remembered, so that whenever a new listener is attached, it replays that update for that listener. Also lets you access the "current value" directly. Otherwise identical to EventStream. I expect to define a generic way to create a ReactiveValue from any object property (this is easy to define on top of Object.observe).

**???** - a more general "lazy/async ordered container" data structure. Single-listener, lossless. I haven't yet figured out the API for this, though.

Note that I am explicitly ignoring the use-case of "binary" or "IO" streams, like Node's Stream class. These are similar in nature to event streams, but have their own quirks and needs that make it not a great idea to try and bodge them together with the rest. There's a good chance that a binary stream API will end up with a similar API surface, though, so keeping that in mind when figuring out all the names is probably a good idea.

EventStream API

An EventStream is an object representing a value changing over time. That's it - it's an extremely simple, general concept, but also extremely powerful and useful when applied widely.

The EventStream API looks like this in WebIDL:

callback StreamInit = void (StreamResolver resolver);
callback AnyCallback = any (optional any value);
typedef (EventStream or Future or Iterable) StreamLike;

[Constructor(StreamInit init)]
interface EventStream {
  EventStream then(optional AnyCallback? updateCB = null, optional AnyCallback? rejectCB = null, optional AnyCallback? completeCB = null);
  EventStream catch(optional AnyCallback rejectCB);
  EventStream complete(optional AnyCallback completeCB);
}

Event streams have a then method, which you use to get updates from the stream, or catch any errors the stream throws. Additionally, some streams can become complete and not push any more updates. This function takes up to three callbacks: one that listens to updates, one that listens for errors, and one that is called if the stream ever ends without error (not all streams do so). Any of the callback arguments may be null instead, which just doesn't register a callback for that purpose. Any of the callbacks may be called multiple times, though in common cases you'll get multiple calls of updateCB and at most one call to rejectCB or completeCB.

then returns a brand new event stream, one constructed from the very callbacks you provided, just like Futures. The exact behavior depends on the callback.

For updateCB:

  • If you return a value, the output stream pushes that value as an update.
  • If you return a stream-like, the output stream adopts that stream. The meaning of "adopt a stream" is defined below.
  • If you throw an error, the output stream is rejected with that error.

For rejectCB and completeCB:

  • If you return a value, the output stream pushes that value as a final update, then completes.
  • If you return a stream-like, the output stream adopts the stream, then completes.
  • If you throw an error, the output stream is rejected with that error.

(The "return a value" semantics are all exactly equivalent to instead returning a streamlike with Future.of(value).)

Note that completeCB being called does not automatically complete the output stream. The output stream is only completed when the input stream and all adopted streams are completed.

When a stream "adopts" another stream, it partially merges its state with the adopted stream. Whenever the adopted stream pushes an update, the parent stream pushes the same value as an update. If the adopted stream is rejected, the parent stream is rejected (which can be caught by the rejectCB on the parent stream). The adopted stream also prevents the parent stream from "completing" normally - instead, the parent stream completes only when it would complete normally and all of its adopted streams have completed.

(Right now, updates that get pushed in a tick prior to when you register a then callback get lost. Should we have a flag that causes the stream to buffer until it gets its first listener?)

For convenience, EventStream#reject and EventStream#complete are provided, and are equivalent to calling then and only providing the given callback.

Creating Streams

Most of the time, streams will be returned by other operations, so you don't have to worry about creating them yourself. When you do, though, there are several methods to do so:

partial interface EventStream {
  static EventStream of(any... values);
  static EventStream from(any value);
  static EventStream reject(any reason);
  static EventStream listen(EventListener target, DOMString event);
}

The EventStream.from function converts a stream-like into a stream, just like the Array.from convenience function does for iterables. If the passed value isn't stream-like, this is identical to EventStream.of. Any functions that expect a stream should pass their argument through EventStream.from first, just in case.

(A Future becomes a stream that either pushes one update and completes (when the future accepts) or pushes no updates and rejects (when the future rejects). A ProgressFuture is the same, but progress updates are pushed as stream updates as well. An iterable becomes a stream that pulls all the values out of the iterable and pushes them as updates one-by-one, then completes.)

EventStream.of creates a completed stream out of its arguments, just like Array.of does for iterables. (It's the monadic "return/pure/point" operation.) It creates an event stream, pushes all the arguments into it in order, and then immediately completes.

EventStream.reject returns an event stream which immediately rejects with the given reason.

EventStream.listen automatically converts DOM Events into an event stream. Just specify an object that events are fired at to listen to, and the event you want, and every time that type of event is fired on the object (or bubbles to the object from a descendant), the event object will be pushed as an update to the output stream. This has no effect on the actual event, so you still need to use actual Events if you want to cancel an event/etc.

If none of these suffice, an event stream can be created manually with new EventStream(resolverCB).

interface EventStreamResolver {
  void push(any value);
  void complete(optional any value);
  void adopt(StreamLike value);
  void continueWith(StreamLike value);
  void reject(any value);
};

The constructor immediately returns an event stream, which is then controlled by the resolverCB function.

The resolverCB is called immediately, and is passed a resolver object, which represents the ability to update the stream. This can be passed around to other functions if desired, or just used in the resolverCB.

push takes a value, and pushes it to the stream as an update. complete completes the stream. It optionally takes a value to push as a final update before completing. adopt adopts the stream-like. continueWith adopts the stream-like, and immediately completes the parent stream as well. (It's syntax sugar for r.adopt(s); r.complete();.) reject rejects the stream, with the given reason. If the resolverCB throws an error, that also immediately rejects the stream.

Once complete or reject has been called, the resolver object becomes inert - calling any of the functions silently does nothing. This allows you to, for example, pass the resolver object to multiple functions, allowing them all to update the stream and race to complete/reject - once the first one ends the stream, the rest also lose the ability to update.

Event Stream Combinators

The real value of streams is the ability to manipulate and combine them, something which is non-trivial to do with callback interfaces or DOM Events. There's tons of possibilities for API here, a lot of which we'll have to leave to user-land libraries to fill in.

Before I get into explaining those, though, I'll point out that the basic stream listening operation - then - is also a great stream combiner. If you have a stream of streams, just calling s.then() on it, with no arguments, will "flatten" the event stream for you, producing a single stream that combines the updates from all the streams into one. (If you've got an array of streams, just pass it through EventStream.from first to turn it into a stream.) If you've got a stream of normal values, and a transformation function that'll turn those values into streams (or stream-likes, like Futures or arrays), calling then with the transformation function will similarly make a new stream that pushes updates from all the transformed values.

For example, let's assume that XHR grows a Future-based interface, called getJSON. If you've got an array of values that you want to send to the server and get back results from, you can just run EventStream.from(valArray).then(x=>getJSON(url, x)) and get back a stream that'll push all the results as updates as they come in, then complete itself. If you add a reject callback, you can get informed when any of the XHRs hit an error, too, and choose between making the entire stream fail or gracefully recovering! For example, to provide a default value, just do return EventStream.of({...});. To omit the failing one from the results, do return EventStream.of();, which makes an empty stream.

(If you're familiar with monads, this is because then is the monadic operation for event streams. I like the developing convention that of and then are the names for the monad functions in JS.)

Here's a start at some more interesting things that might belong in the core spec, though:

partial interface EventStream {
  EventStream filter(AnyCallback filterCB);
  EventStream switch(optional AnyCallback filterCB);
  EventStream map(AnyCallback mapCB);
  EventStream forEach(AnyCallback listenCB);
  EventStream throttle(number delay, optional number timeout = Infinity);

  Future next(optional AnyCallback filterCB);
  Future last();
}

For all of the functions defined here that return a new EventStream, throwing an error in the callback will automatically reject the output stream, just like in then.

EventStream#filter() takes a filter function, and returns a new stream that only contains the updates that the filter returned true for.

EventStream#map() is similar to then, but specialized for functions that don't return a stream-like. That is, no matter what you return, it just gets emitted as a single value in the output stream. This makes it easy to return arrays or Futures, which will get interpreted as a stream-like by then unless you manually wrap them in a dummy stream. (This is the functor operation for streams.)

EventStream#forEach() is used to "tap into" an existing stream, without modifying it - the results of its callback are completely ignored, so the output stream updates with the exact same values as the input stream. The only effect the callback can have on the output stream is to cause it to reject, by throwing an error.

EventStream#switch() is useful when you want to ignore "earlier" event streams once "later" ones have started giving out data. It's designed to take a stream of streams (or take a stream of values, plus a conversion function that returns streams, like then() does, defaulting to EventStream.from), and do a different type of interleaving - it stops listening to earlier streams once later streams have started putting out values.

This ACM article (scroll down to the part starting with Figure 3) has a great example of switch() - they want to watch a text input's value for changes, and as the user types, fire off XHRs for autocomplete suggestions and use them. However, if one XHR is slow, and the XHR for the next text input event is fast, they want to make sure they ignore the slow one, because it's obsolete now (if the user types "a", then "ab", you don't want to first show "ab" autocomplete results, then interrupt yourself and start showing "a" autocomplete results). This is complex given today's code, but with streams it's simple and easy (assuming a few convenience functions): watchTextInput(input).switch(t=>getJSON(url,t)).forEach(updateAutocomplete). DONE. (watchTextInput is a stand-in for some way to get a stream out of certain kinds of events. getJSON is an assumed future addition to the XHR spec that returns a Future for the result, which is a stream-like.)

(That article is great for multiple reasons - the figures are great visual illustrations of the effects of several functions: its "Where" is our "filter", its "Select" is our "map", its "SelectMany" is our "then".)

EventStream#throttle() slows a stream down, filtering updates that come in too fast. Whenever its input stream updates, it delays pushing it to its output stream for "delay" milliseconds, and if a new update comes in before the time is up, it simply throws away that update and restarts the clock for the new update. If "timeout" milliseconds have passed while the stream was continually idling, waiting for the stream to slow down, it goes ahead and pushes the most recent pending update. For example, the previous switch() example could probably use a throttle, placed between the watchTextInput() and the switch(), so you won't kick off 10 XHRs in quick succession when the user is typing fast; instead you'll just get the 10th update and XHR that.

ReactiveValue API

A ReactiveValue is just like an EventStream, except it maintains a memory of the last update as its "current value". The updates, thus, represent updates to the underlying value.

interface ReactiveValue {
  /* all of the EventStream methods */

  attribute any value;
  EventStream updates();
  EventStream diffs();
  ReactiveValue squash();

  static ReactiveValue watch(any object, DOMString property);
}

ReactiveValue is nearly identical to EventStream, with all the same methods, except for a few small differences. The first is that, obviously, all of the methods return ReactiveValues, not EventStreams.

The next change is that a ReactiveValue, whenever it's used as an input stream, pushes as its first update the current value. This happens automatically, even if a ReactiveValue is created manually, even if it's used in an EventStream function.

Finally, if using the explicit constructor, the return value of the resolverCB is taken as the stream's initial value. (In an EventStream, the return value of the resolverCB is ignored.)

It also adds a few things. First, the value attribute on the ReactiveValue holds the current value of the stream. This is a normal attribute, and is automatically changed by the ReactiveValue's handling of updates.

(Does this mean that the value changes syncly, even though you're not able to respond to the updates normally until a future tick? Or should this only update when update listeners are handled?)

The ReactiveValue#updates function returns a new EventStream that is identical to the ReactiveValue it was called on, except without the special "current value" handling. That is, if you call a combinator on it, you won't get the special "first update is the current value" behavior.

The ReactiveValue#diffs function is similar, but still gives you information about the previous value. Whenever the input stream updates, the output stream updates with an object containing an old key holding the old value, and a new key holding the input stream's update value.

The ReactiveValue#squash function avoids accidentally doing work on stale data. It returns a new ReactiveValue that mirrors the input stream, but with some special behavior for its updateCB - whenever an update would be pushed to a listener, it first checks to see if there are any pending updates still waiting on the event queue. If so, rather than queueing a new one, it just updates the old one to the new value. In this way, if multiple updates happen between successive calls to your updateCB, you'll only get the latest value, rather than stale data.

(Should this be the default, with a function that instead opts out? Or maybe we just rely on updates to cover the "I want every single change" use-cases?)

Finally, the static watch function lets you turn properties on arbitrary objects into value streams. For example, in my proposal for Font Load Events using Futures, I just expose a simple loadStatus property for whether the document is currently loading any fonts or not. If you want to watch this (for example, to provide some UI telling the user fonts are currently loading), you would just do:

ReactiveValue.watch(document.fonts, 'loadStatus')
  .then(updateLoadingUI);

The returned stream updates immediately with the current value of the property, and then updates again any time the value changes. If the property is removed, the stream updates to undefined. If the object is deleted, the stream rejects.

This can be implemented on top of Mutation Observers without a ton of trouble, but this is super-simple and easy for the common case.

Element Queries

Last updated:

I'm getting more and more requests for "Element Queries", and it's getting annoying to address them via Twitter, so I thought I'd discuss the limitations and challenges of this idea right here and just point people to it when required.

What's an Element Query?

The idea of Element Queries is pretty simple - it's like a Media Query (specifically, the min-width/etc queries), but for a parent or ancestor element, rather than the viewport.

This way, you could create a "component" that is styled one way when it's in a narrow container (like a sidebar), but another way when it's got some breathing room (like in the main content of the page), without having to manually specify what kinds of containers are each type - it just naturally happens due to the width of the respective containers.

Circularity

The first and most obvious problem with EQs is circularity.

There are lots of ways in CSS for an element's width to depend on the size of its contents. If the contents can then respond dramatically to the size of the element, you've potentially got problems. For example, say you had something like this:

.container {
  float: left;
}
.child {
  width: 500px;
}
.container:min-width(450px) > .child {
  width: 400px;
}

In this simple example, the container's size is determined by the size of its contents, because it's floating. By default, its only child is 500px wide. However, whenever the container is greater than 450px wide, the child is 400px wide.

In other words, if the child is 500px wide (thus making the container also 500px wide), then the child becomes 400px wide. But if the child is 400px wide (thus making the container also 400px wide), the child becomes 500px wide. Circular dependency!

This already happens today in some circumstances, such as an overflow:auto container with contents that have an aspect-ratio. If the aspect ratio is "tall", then reducing the width will shrink the height by a larger amount. If the content just barely overflows, scrollbars will pop in, reducing the width of the container, which shrinks the height of the contents enough that they no longer overflow. Thus, you don't need a scrollbar, and it disappears, making the content wider and taller, so it overflows, so you need a scrollbar, so the content becomes narrower and shorter, and you don't need a scrollbar...

We could potentially do a similar thing with EQs, but it's much less clear how it would work, since there are so many more variables than with scrollbars.

Avoiding Circularity

It would be much better if we could just avoid circularity entirely, and the web platform already shows us how to do so: iframes! Iframes are elements in the page, but they establish a brand new viewport for their contents, so you can use normal MQs inside of them to style the contents differently based on how big the iframe is, just like an EQ. They avoid circularity issues by locking down their own width/height - the size of an iframe doesn't depend on its contents at all. If you don't explicitly specify their size, iframes default to a size of 300×150.

Taking this over to EQs, we'd need some way to declare that an element was a "viewport element", and didn't pay attention to its children at all when sizing, instead defaulting to some specified size (probably 300×150 to be consistent) when necessary. Then, its contents could be allowed to refer to its size in selectors, and style themselves accordingly.

Unfortunately, this still has issues...

Viewport Elements

Even if you avoid circularity, "viewport elements" have further issues in implementation.

The concept of EQs already requires a bit of weirdness, in that you can't run selectors once, first, and then do all your layout - you have to go back and forth, doing layout and then selectors and then layout again. However, because of iframes, this is already something you need to do, and further, you know up-front that you don't need to lay out an iframe's contents or run its selectors/MQs until the outer document is finished laying out, so you're never throwing away any work and starting over, just putting some off until later.

The most natural way to declare an element to be a "viewport element", though - a new CSS property or value - breaks this. You'd need to do run selectors on the whole page, run the cascade, figure out which elements are viewport elements from that, and then rerun selectors on the children again. This is wasted work, and it's slow and annoying to deal with in the implementation.

Boris Zbarsky, Moz hacker, says he'd be okay with viewport elements and EQs if they could be detected from the markup. This means adding some styling information to your document markup, which isn't ideal. But maybe it's okay? I dunno.

So, that's the state of the art right now.