Edd Mann Developer

Using Generators for Promise Coroutines in JavaScript

In a previous article I demonstrated how a new feature from ES2016 (async/await functions) could be used to help coordinate promises and execution. However, we are instead able to use Generators (introduced in ES2015) to provide this feature, minus the syntactic sugar these two new keywords provide.

const spawn = (fn) => (...args) => {
  const gen = fn(...args);
  const cont = (res) => res.done
    ? res.value
    : Promise.resolve(res.value).then(
        val => cont(gen.next(val)),
        err => cont(gen.throw(err)));

  return cont(gen.next());
}

Passing in a Generator function and yielding in-place of await, we are able to take advantage of the Generators control we have for resuming its execution. This allows us to wait for the resolution or rejection of the yielded promise before continuing to the next yield or final value.

Example

Below is a contrived example of using co-routines to coordinate the returned result of a collection of random promises (similar to a Promise.all use-case).

const randomPromise = (value) =>
  new Promise((res, rej) => setTimeout(Math.random() > 0.3 ? res : rej, 1000, value));

const run = spawn(function*(values) {
  const results = [];

  for (const val of values.map(randomPromise)) {
    try {
      results.push(yield val);
    } catch (e) {
      results.push(`${e} ✗`);
    }
  }

  return results;
});

console.time('run');
run(['foo', 'bar', 'baz'])
  .then((x) => {
    console.log(x);
    console.timeEnd('run');
  })
  .catch(console.error);
// [ 'foo', 'bar ✗', 'baz' ]
// run: 1012ms