yuri kilochek yuri kilochek - 3 months ago 15
Javascript Question

ES6 asynchronous generator result

ES6 has generators that return iterators:

function* range(n) {
for (let i = 0; i < n; ++i) {
yield i;
}
}

for (let x of range(10)) {
console.log(x);
}


There is a proposal for asynchronous functions that return Promises:

async function f(x) {
let y = await g(x);
return y * y;
}

f(2).then(y => {
console.log(y);
});


So what happens if I combine the two, like this:

async function* ag(n) {
for (let i = 0; i < n; ++i) {
yield i;
}
}


What does it return? Is it
Promise<Iterator<Item>>
?
Iterator<Promise<Item>>
? Something else? How do I consume it? I imagine there should be a corresponding
for
loop, what will iterate over its result asynchronously, something like:

for (await let x of ag(10)) {
console.log(x);
}


which waits for each item to become available before trying to access the next one.

Answer

Promise<Iterator<Item>> or Iterator<Promise<Item>>?

Neither. It's still not approved, but current implementations return something else. Kris Kowal has written an about async generators, and references Jafar Husain's AsyncGenerator proposal for ES7.

Let's define some types (simplified):

interface Iterator<T> {
  Iteration<T> next();
}

type Iteration<T> = { done: boolean, value: T }

We are looking for something that can be used like this:

for (;;) {
    var iteration = await async_iterator.next();
    if (iteration.done) {
        return iteration.value;
    } else {
        console.log(iteration.value);
    }
}

An Iterator<Promise<T>> produces synchronous iterations, whose values are Promises. It could be used like this:

for (;;) {
    var iteration = iterator_promise.next();
    if (iteration.done) {
        return await iteration.value;
    } else {
        console.log(await iteration.value);
    }
}

A Promise<Iterator<T>> is just a regular synchronous iterator, starting in the future:

var iterator = await promise_iterator;
for (;;) {
    var iteration = iterator.next();
    if (iteration.done) {
        return iteration.value;
    } else {
        console.log(iteration.value);
    }
}

So neither Iterator<Promise<T>> nor Promise<Iterator<T>> was suitable. Currently async generators return AsyncIterators instead:

interface AsyncIterator<T> {
  Promise<Iteration<T>> next();
}

Which perfectly makes sense. Moving to the next element of the iterator is the asynchronous operation, and this can be used exactly like we wanted.

How do I consume Async Generators?

Babeljs.io already compiles async generators. Babeljs.io/repl example:

EDIT: No preset on babeljs.io compiles async generators since babel 6, babel-plugin-transform-regenerator supports it with {asyncGenerators:true} option.

function delay(timeout, val) {
  return new Promise(resolve => setTimeout(resolve, timeout, val));
}

async function* asyncGenerator() {
  for (var i = 0; i < 5; i++) {
    await delay(500);
    yield i;
  }
}

async function forAwait(iter, fn) {
  for (;;) {
    let iteration = await iter.next();
    if (iteration.done) return iteration.value;
    await fn(iteration.value);
  }
}


async function main() {
  console.log('Started');
  await forAwait(asyncGenerator(), async item => {
    await delay(100);
    console.log(item);
  });
  console.log('End');
}

main();

There is a proposal for a convenient for await loop for async iterators (described at Async iteration):

for await (let line of readLines(filePath)) {
    print(line);
}

Update:

Unfortunately, async-await didn't become a part of ECMAScript 2016. At least await is mentioned a reserved word for future use.

Comments