Search code examples
javascriptasync-awaitecmascript-6generatorecmascript-next

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.


Solution

  • 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. EDIT: We have tc39 proposal and babel support!

    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.

    EDIT: see transform-async-generator-functions babel 6 plugin.

    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.

    Update:

    Related proposals: