I am trying to convert a normal function to an async generator function, it needs to have the same props and prototype.
The way that I did it so far was by copying all the descriptors from the async generator function and by using Object.setPrototypeOf
function normalFunc () {
//...
}
async function * asyncGenFunc () {
//...
}
Object.defineProperties(normalFunc, Object.getOwnPropertyDescriptors(asyncGenFunc))
Object.setPrototypeOf(normalFunc, Object.getPrototypeOf(asyncGenFunc))
As I understand it, Object.setPrototypeOf is slow even though I can't see the slowness myself. Is there a better way or is this way not slow in the specific scenario and the tip in MDN isn't about this case.
EDIT: As for the why do I want to do this, here is a very basic version of the function I am making:
const errorHandle = function (func) {
return function(...args) {
try {
let result
result = func.apply(this, args)
if (isObject(result) && result.catch !== undefined) {
result = result.catch(err => console.error(err))
}
return result
} catch(err) {
console.error(err)
}
}
}
const handledAsyncGenFunc = errorHandle(async function * (){})
I want handledAsyncGenFunc to behave exactly the same as a the original async generator function, but to be error-handled. Don't judge the example too much, I only added the bare minimum for the question.
From what I can gather your after a kind of middleware for async generators.
This is actually easier than you think, you can just create wrapper function that that just passes re yields inside a loop.
eg.
const wait = ms => new Promise(r => setTimeout(r, ms));
async function *gen1() {
yield 1;
await wait(1000);
yield 2;
await wait(1000);
throw "error in gen1";
}
function *gen2() {
yield 1;
throw "error in gen2 (normal generator)";
}
async function *handleError(fn) {
try {
for await (const x of fn) yield x;
// yield * fn;
} catch (e) {
console.log('Caught a generator error (async or normal):');
console.error(e);
//you could also re-throw here
//if your just want to log somewhere
//and keep program logic
//throw e;
}
}
async function test1() {
for await (const a of handleError(gen1())) {
console.log(a);
}
}
async function test2() {
for await (const a of handleError(gen2())) {
console.log(a);
}
}
test1().finally(() => test2());
One thing to note, when you wrap the function you always assume it's an async generator that returned, even if you pass a normal one. This is because await will work with none Promise's, but of course it's impossible for this to work the other way round. IOW: you will need to use for await (x of y) { on the wrapper function and not just for (x of y) {
I am trying to visualize the synchronicity of for...await loops in JavaScript.
It appears that in an async generator function both yield and await will stop progress and cue up a continuation on a microtask.
In other words: yielding is always asynchronous in this context, even if you don't use the await keyword.
Is this correct? What about async generator functions outside of the context of for...await loops?
Below a tight microtask loop is created using then, in an attempt to see how tasks are interleaved.
function printNums() {
let counter = 0
function go() {
console.log(counter++)
if(counter < 10) Promise.resolve().then(go)
}
Promise.resolve().then(go)
}
printNums()
async function asyncFn() {
console.log('inside asyncFn 1')
await null
console.log('inside asyncFn 2')
}
asyncFn()
const asyncIterable = {
async *[Symbol.asyncIterator]() {
console.log('inside asyncIterable 1')
yield '⛱'
console.log('inside asyncIterable 2')
await null
console.log('inside asyncIterable 3')
yield '🐳'
yield '🥡'
}
}
async function printAsyncIterable() {
for await(let z of asyncIterable) {
console.log(z)
}
}
printAsyncIterable()
In other words: yielding is always asynchronous in this context, even if you don't use the await keyword
Yes. Inside an asynchronous generator function, the expression
yield value
behaves as if you had written
await (yield (await value))
You cannot yield promises, and you cannot receive promises, they always get automatically unwrapped by the next() call.
MDN has an example in which a generator function is marked as async, and yield is used without await.
Why is await not necessary in this example?
const myAsyncIterable = {
async* [Symbol.asyncIterator]() {
yield "hello"
yield "async"
yield "iteration!"
}
}
Just because an function is async doesn't mean that await is required. I think this example has async just to show that you could easily insert an awaited Promise into it.
Something with a Symbol.asyncIterator can be iterated over asynchronously if needed, but it doesn't have to have any asynchronous operations. You could even replace the async function with a standard generator function, on the Symbol.asyncIterator property, and still have things work just fine:
const myAsyncIterable = {
*[Symbol.asyncIterator]() {
yield "hello"
yield "async"
yield "iteration!"
}
};
(async () => {
for await (const item of myAsyncIterable) {
console.log(item);
}
})();
Looking at the specification, after the iterator is retrieved from the object, it doesn't matter whether the iterator is sync or async. An async generator function will result in an async iterator; a sync generator function will result in a sync iterator. See the final step 7 of the spec here - GetIterator will be invoked, and whatever object that's on Symbol.asyncIterator will be returned, if it exists, without regard to whether it's actually async or not.
With the iterator, when iterated over with for await, each value returned by the iterator will be awaited:
b. If iteratorKind is async, then set nextResult to ? Await(nextResult).
And it's perfectly fine to await something that isn't a Promise, it's just odd:
(async () => {
const item = await 'foo';
console.log(item);
})();
await will unwrap a Promise if the expression to its right is a Promise, and will leave it unchanged otherwise.
I wanna write a function but I don't know which one of them is better:
function* call() {
try {
const a = yield api(1);
const b = yield api(2);
const c = yield api(3);
const d = yield api(4);
return [a, b, c, d];
} catch (e) {
console.log(e);
}
}
or Async/Await:
async function call() {
try {
const a = await api(1);
const b = await api(2);
const c = await api(3);
const d = await api(4);
return [a, b, c, d];
} catch (e) {
console.log(e);
}
}
Both of them works well, I don't know which one of them is better or what is the difference between them.
No they are not exactly the same, the differences are as follows:
The call() invocation with return you an iterator object for the generator function* whereas the async function will return you an array wrapped in a promise.
If you don't pass any value to the iterator.next() calls after getting it from the generator invocation, the resulting array you are returning from the generator function will have four undefined values. But in the async version you will get back the values returned from the api() calls in the Promise wrapped array.
Also the yield statements would return you the values when you iterate through the iterator, but in the async function the await will wait for the Promise returned by api() calls to be resolved then move on the next await else if the value is not a Promise from the api() call it will convert it to resolved Promise and the value of the await expression will become the value of the resolved Promise.
These three points can be illustrated below by a snippet.
Generator function:
function* call() {
try {
const a = yield 1;
const b = yield 2;
const c = yield 3;
const d = yield 4;
return [a, b, c, d];
} catch (e) {
console.log(e);
}
}
const itr = call();
//next() is not invoked with any params so the variables a, b, c, d will be undefiend
console.log(itr.next().value);
console.log(itr.next().value);
console.log(itr.next().value);
console.log(itr.next().value);
console.log(itr.next().value);
Async function:
async function call() {
try {
const a = await 1;
const b = await 2;
const c = await 3;
const d = await 4;
return [a, b, c, d];
} catch (e) {
console.log(e);
}
}
//call() will return a Promise
call().then(data => console.log(data));
This is called a generator function btw.
function* call()
The most important difference between async/await and generators is
that generators are natively supported all the way back to Node.js
4.x, whereas async/await requires Node.js >= 7.6.0. However, given that Node.js 4.x has already reached end-of-life and Node.js 6.x will
reach end-of-life in April 2019, this difference is rapidly becoming
irrelevant.
source: https://thecodebarbarian.com/the-difference-between-async-await-and-generators
Aysnc/Await offers a more concise approach to dealing with concurrency. But the generator functions offer more flexibility.
The choice depends on what you want to achieve, but in most cases, it makes sense to use async/await specially if you are using a modern version of NodeJs but
I was just reading this fantastic article «Generators» and it clearly highlights this function, which is a helper function for handling generator functions:
function async(makeGenerator){
return function () {
var generator = makeGenerator.apply(this, arguments);
function handle(result){
// result => { done: [Boolean], value: [Object] }
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value).then(function (res){
return handle(generator.next(res));
}, function (err){
return handle(generator.throw(err));
});
}
try {
return handle(generator.next());
} catch (ex) {
return Promise.reject(ex);
}
}
}
which I hypothesize is more or less the way the async keyword is implemented with async/await. So the question is, if that is the case, then what the heck is the difference between the await keyword and the yield keyword? Does await always turn something into a promise, whereas yield makes no such guarantee? That is my best guess!
You can also see how async/await is similar to yield with generators in this article where he describes the 'spawn' function ES7 async functions.
Well, it turns out that there is a very close relationship between async/await and generators. And I believe async/await will always be built on generators. If you look at the way Babel transpiles async/await:
Babel takes this:
this.it('is a test', async function () {
const foo = await 3;
const bar = await new Promise(resolve => resolve('7'));
const baz = bar * foo;
console.log(baz);
});
and turns it into this
function _asyncToGenerator(fn) {
return function () {
var gen = fn.apply(this, arguments);
return new Promise(function (resolve, reject) {
function step(key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(function (value) {
return step("next", value);
}, function (err) {
return step("throw", err);
});
}
}
return step("next");
});
};
}
this.it('is a test', _asyncToGenerator(function* () { // << now it's a generator
const foo = yield 3; // <<< now it's yield, not await
const bar = yield new Promise(resolve => resolve(7));
const baz = bar * foo;
console.log(baz);
}));
you do the math.
This makes it look like the async keyword is just that wrapper function, but if that's the case then await just gets turned into yield, there will probably be a bit more to the picture later on when they become native.
You can see more of an explanation for this here:
https://www.promisejs.org/generators/
yield can be considered to be the building block of await. yield takes the value it's given and passes it to the caller. The caller can then do whatever it wishes with that value (1). Later the caller may give a value back to the generator (via generator.next()) which becomes the result of the yield expression (2), or an error that will appear to be thrown by the yield expression (3).
async-await can be considered to use yield. At (1) the caller (i.e. the async-await driver - similar to the function you posted) will wrap the value in a promise using a similar algorithm to new Promise(r => r(value) (note, not Promise.resolve, but that's not a big deal). It then waits for the promise to resolve. If it fulfills, it passes the fulfilled value back at (2). If it rejects, it throws the rejection reason as an error at (3).
So the utility of async-await is this machinery that uses yield to unwrap the yielded value as a promise and pass its resolved value back, repeating until the function returns its final value.
what the heck is the difference between the await keyword and the yield keyword?
The await keyword is only to be used in async functions, while the yield keyword is only to be used in generator function*s. And those are obviously different as well - the one returns promises, the other returns generators.
Does await always turn something into a promise, whereas yield makes no such guarantee?
Yes, await will call Promise.resolve on the awaited value.
yield just yields the value outside of the generator.
tl;dr
Use async/await 99% of the time over generators. Why?
async/await directly replaces the most common workflow of promise chains allowing code to be declared as if it was synchronous, dramatically simplifying it.
Generators abstract the use case where you would call a series of async-operations that depend on each other and eventually will be in a "done" state. The most simple example would be paging through results that eventually return the last set but you would only call a page as needed, not immediately in succession.
async/await is actually an abstraction built on top of generators to make working with promises easier.
See very in-depth Explanation of Async/Await vs. Generators
Try this test programs which I used to understand await/async with promises.
Program #1: without promises it doesn't run in sequence
function functionA() {
console.log('functionA called');
setTimeout(function() {
console.log('functionA timeout called');
return 10;
}, 15000);
}
function functionB(valueA) {
console.log('functionB called');
setTimeout(function() {
console.log('functionB timeout called = ' + valueA);
return 20 + valueA;
}, 10000);
}
function functionC(valueA, valueB) {
console.log('functionC called');
setTimeout(function() {
console.log('functionC timeout called = ' + valueA);
return valueA + valueB;
}, 10000);
}
async function executeAsyncTask() {
const valueA = await functionA();
const valueB = await functionB(valueA);
return functionC(valueA, valueB);
}
console.log('program started');
executeAsyncTask().then(function(response) {
console.log('response called = ' + response);
});
console.log('program ended');
Program #2: with promises
function functionA() {
return new Promise((resolve, reject) => {
console.log('functionA called');
setTimeout(function() {
console.log('functionA timeout called');
// return 10;
return resolve(10);
}, 15000);
});
}
function functionB(valueA) {
return new Promise((resolve, reject) => {
console.log('functionB called');
setTimeout(function() {
console.log('functionB timeout called = ' + valueA);
return resolve(20 + valueA);
}, 10000);
});
}
function functionC(valueA, valueB) {
return new Promise((resolve, reject) => {
console.log('functionC called');
setTimeout(function() {
console.log('functionC timeout called = ' + valueA);
return resolve(valueA + valueB);
}, 10000);
});
}
async function executeAsyncTask() {
const valueA = await functionA();
const valueB = await functionB(valueA);
return functionC(valueA, valueB);
}
console.log('program started');
executeAsyncTask().then(function(response) {
console.log('response called = ' + response);
});
console.log('program ended');
In many ways, generators are a superset of async/await. Right now async/await has cleaner stack traces than co, the most popular async/await-like generator based lib. You can implement your own flavor of async/await using generators and add new features, like built-in support for yield on non-promises or building it on RxJS observables.
So, in short, generators give you more flexibility and generator-based libs generally have more features. But async/await is a core part of the language, it's standardized and won't change under you, and you don't need a library to use it. I have a blog post with more details on the difference between async/await and generators.
The yield+gen.next()-as-a-language-feature can be used to describe (or implement) the underlying control-flow that await-async has abstracted away.
As other answers suggest, await-as-a-language-feature is (or can be thought of) an implementation on top of yield.
Here is a more intutive understanding for that:
Say we have 42 awaits in an async function, await A -> await B -> ...
Deep down it is equivalent to having yield A -> tries resolve this as a Promise [1]
-> if resolvable, we yield B, and repeat [1] for B
-> if not resolveable, we throw
And so we end up with 42 yields in a generator. And in our controller we simply keep doing gen.next() until it is completed or gets rejected. (ie this is the same as using await on an async function that contains 42 await.)
This is why lib like redux-saga utilizes generator to then pipe the promises to the saga middleware to be resolved all at one place; thus decoupling the Promises constructions from their evaluations, thus sharing close resemblance to the Free Monad.
The idea is to recursively chain then() calls to replicate the behavior of await which allows one to invoke async routines in a synchronous fashion. A generator function is used to yield back control (and each value) from the callee to the caller, which happens to be the _asyncToGenerator() wrapper function.
As mentioned above, this is the trick that Babel uses to create polyfills. I slightly edited the code to make it more readable and added comments.
(async function () {
const foo = await 3;
const bar = await new Promise((resolve) => resolve(7));
const baz = bar * foo;
console.log(baz);
})();
function _asyncToGenerator(fn) {
return function () {
let gen = fn(); // Start the execution of the generator function and store the generator object.
return new Promise(function (resolve, reject) {
function step(func, arg) {
try {
let item = gen[func](arg); // Retrieve the function object from the property name and invoke it. Similar to eval(`gen.${func}(arg)`) but safer. If the next() method is called on the generator object, the item value by the generator function is saved and the generator resumes execution. The value passed as an argument is assigned as a result of a yield expression.
if (item.done) {
resolve(item.value);
return; // The executor return value is ignored, but we need to stop the recursion here.
}
// The trick is that Promise.resolve() returns a promise object that is resolved with the value given as an argument. If that value is a promise object itself, then it's simply returned as is.
return Promise.resolve(item.value).then(
(v) => step("next", v),
(e) => step("throw", e)
);
} catch (e) {
reject(e);
return;
}
}
return step("next");
});
};
}
_asyncToGenerator(function* () { // <<< Now it's a generator function.
const foo = yield 3; // <<< Now it's yield, not await.
const bar = yield new Promise((resolve, reject) => resolve(7)); // <<< Each item is converted to a thenable object and recursively enclosed into chained then() calls.
const baz = bar * foo;
console.log(baz);
})();