Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 335
yuri kilochek Avatar asked Jan 09 '16 12:01

yuri kilochek


2 Answers

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:

  • https://tc39.github.io/ecmascript-asyncawait/
  • https://github.com/tc39/proposal-async-iteration
like image 175
Tamas Hegedus Avatar answered Sep 21 '22 18:09

Tamas Hegedus


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)
    }
}
like image 35
Stephen Quan Avatar answered Sep 20 '22 18:09

Stephen Quan