Reputation: 13486
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.
Upvotes: 5
Views: 559
Reputation: 25906
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)
}
}
Upvotes: 1
Reputation: 29906
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 AsyncIterator
s 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.
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:
Upvotes: 5
Reputation: 3593
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.
Upvotes: 0