No promises: asynchronous JavaScript with only generators

Two ECMAScript 6 [1] features enable an intriguing new style of asynchronous JavaScript code: promises [2] and generators [3]. This blog post explains this new style and presents a way of using it without promises.

It would be great if this style of invocation also worked for functions that perform tasks (such as downloading a file) asynchronously. For that to work, execution of the previous code would have to pause until func() returns with a result.

Before ECMAScript 6, you couldn’t pause and resume the execution of code, but you could simulate it, by putting console.log(result) into a callback, a so-called continuation[4]. The continuation is triggered by asyncFunc(), once it is done:

Note that you only need use caller = yield and caller.success(···) in asynchronous functions that use callbacks. If an asynchronous function only calls other asynchronous functions (via yield) then you can simply explicitly return a value.

One important feature is missing: support for calling async functions implemented via promises. It would be easy to add, though – by adding another case to runYieldedValue().

Couroutines[5] are a single-threaded version of multi-tasking: Each coroutine is a thread, but all coroutines run in a single thread and they explicitly relinquish control via yield. Due to the explicit yielding, this kind of multi-tasking is also called cooperative (versus the usual preemptive multi-tasking).

Generators are shallow co-routines [6]: their execution state is only preserved within the generator function: It doesn’t extend further backwards than that and recursively called functions can’t yield.

The code for asynchronous JavaScript without promises that you have seen in this blog post is purely a proof of concept. It is completely unoptimized and may have other flaws preventing it from being used in practice.

But coroutines seem like the right mental model when thinking about asynchronous computation in JavaScript. They could be an interesting avenue to explore for ECMAScript 2016 (ES7) or later. As we have seen, not much would need to be added to generators to make this work:

caller = yield is a kludge.

Similarly, having to report results and errors via callbacks is unfortunate. It’d be nice if return and throw could always be used, but they don’t work inside callbacks.

Q is a promise library and polyfill that includes the aforementioned Q.spawn(), which is based on promises.

co brings just the spawn() functionality and relies on an external Promise implementation. It is therefore a good fit for environments such as Babel that already have Promises.

Babel has a first implementation of async functions (as proposed for ECMAScript 2016). Under the hood, they are translated to code that is similar to spawn() and based on Promises. However, if you use this feature, you are leaving standard territory and your code won’t be portable to other ES6 environments. Async functions may still change considerably before they are standardized.