ES6 asynchronous generator result
Asked Answered
C

3

5

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.

Cordey answered 9/1, 2016 at 12:57 Comment(2)
Hmmmm, too early to answer this question, as async/await proposal is not yet approved.Woodborer
An async iterator - there's a sp c - I'm on mobile but it's already decided for the most part. It's a generator whose next returns a promise.Putput
F
5

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:

Faun answered 9/1, 2016 at 14:9 Comment(2)
But how to "delegate yield" an async generator? Neither yield* await nor await yield* fits.Borroff
@Borroff In the proposal it is mentioned: The behavior of yield* is modified to support delegation to async iterators.. But one might ask how do you then delegate to regular generator? Ultimately you can always use a yield in a for loop.Faun
F
1

Lots have changed since this post was written. Promises, iterators/generators and async/await syntax are all part of the standard. Let's take a look at the evolution of running a simple async operation (e.g. setTimeout) over the different methods.

Let's consider a simple Promise wrapper to the setTimeout function. Then, we can implement a simple Promise chain to console.log messages with a sleep delay.

function sleep(delay) {
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, delay);
    } );
}

console.log('one');
sleep(1000)
.then( function () {
    console.log('two');
    return sleep(1000);
} )
.then( function () {
    console.log('three');
} );

Now let's consider rewriting the above Promise chain using async/await syntax:

function sleep(delay) {
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, delay);
    } );
}

(async function () {
    console.log('one');
    await sleep(1000);
    console.log('two');
    await sleep(1000);
    console.log('three');
})();

Very nice. Prior to new standards, people were using https://babeljs.io to help transpile from the newer JavaScript standards to an earlier version by rewriting await/async with iterator/generator syntax:

function sleep(delay) {
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, delay);
    } );
}

_asyncToGenerator(function *() {
    console.log('one');
    yield sleep(1000);
    console.log('two');
    yield sleep(1000);
    console.log('three');    
})();

function _asyncToGenerator(fn) {
    return function() {
        var self = this,
        args = arguments
        return new Promise(function(resolve, reject) {
            var gen = fn.apply(self, args)
            function _next(value) {
                asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value)
            }
            function _throw(err) {
                asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err)
            }
            _next(undefined)
        })
    }
}

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
    try {
        var info = gen[key](arg)
        var value = info.value
    } catch (error) {
        reject(error)
        return
    }
    if (info.done) {
        resolve(value)
    } else {
        Promise.resolve(value).then(_next, _throw)
    }
}
Fanaticism answered 23/8, 2022 at 3:59 Comment(0)
F
0

Just thinking: The Iterator-functions have no return-value, so it makes no sense to make them async. Then there is this conceptual gap between these two approaches. - Iterators are pull-based: You call the iterator and invoke the computation of a new Value - Promises are push-based: The Promise pushes a result to it's listener. (once or never)

And while it would make sence in some cases to create an Iterator<Pomise<Item>>

function* f(g){
    for(...){
        let y = await g();
        yield y;
    }
}

I can't think of any case where it would make sense to wrap an Iterator into a Promise. since there is nothing async in Instantiating an Iterator from it's definition.

Futurity answered 9/1, 2016 at 13:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.