ES2017 - Async vs. Yield
I am confused about the current discussion of adding async functions and the keyword await
to the next EcmaScript.
I do not understand why it is necessary to have the async
keyword before the function
keyword.
From my point of view the await
keyword to wait for a result of a generator or promise done, a function's return
should be enough.
await
should simple be usable within normal functions and generator functions with no additional async
marker.
And if I need to create a function what should be usable as an result for an await
, I simply use a promise.
My reason for asking is this good explanation, where the following example comes from:
async function setupNewUser(name) {
var invitations,
newUser = await createUser(name),
friends = await getFacebookFriends(name);
if (friends) {
invitations = await inviteFacebookFriends(friends);
}
// some more logic
}
It also could be done as normal function, if the execution of a function will wait for finishing the hole function until all awaits are fulfilled.
function setupNewUser(name) {
var invitations,
newUser = await createUser(name),
friends = await getFacebookFriends(name);
if (friends) {
invitations = await inviteFacebookFriends(friends);
}
// return because createUser() and getFacebookFriends() and maybe inviteFacebookFriends() finished their awaited result.
}
In my opinion the whole function execution is holding until the next tick (await fulfillment) is done. The difference to Generator-Function is that the next() is triggering and changing the object's value and done field. A function instead will simple give back the result when it is done and the trigger is a function internal trigger like a while-loop.
Solution 1:
I do not understand why it is necessary to have the
async
keyword before the function keyword.
For the same reason that we have the *
symbol before generator functions: They mark the function as extraordinary. They are quite similar in that regard - they add a visual marker that the body of this function does not run to completion by itself, but can be interleaved arbitrarily with other code.
- The
*
denotes a generator function, which will always return a generator that can be advanced (and stopped) from outside by consuming it similar to an iterator. - The
async
denotes an asynchronous function, which will always return a promise that depends on other promises and whose execution is concurrent to other asynchronous operations (and might be cancelled from outside).
It's true that the keyword is not strictly necessary and the kind of the function could be determined by whether the respective keywords (yield(*)
/await
) appear in its body, but that would lead to less maintainable code:
- less comprehensible, because you need to scan the whole body to determine the kind
- more errorprone, because it's easy to break a function by adding/removing those keywords without getting a syntax error
a normal function, whose execution will wait for finishing the hole body until all awaits are fulfilled
That sounds like you want a blocking function, which is a very bad idea in a concurrent setting.
Solution 2:
By marking a function as async
, you're telling JS to always return a Promise.
Because it will always return a Promise, it can also await on promises inside of its own block. Imagine it like one giant Promise chain - what happens internally to the function gets effectively gets bolted on to its internal .then()
block, and what's returned is the final .then()
in the chain.
For example, this function...
async function test() {
return 'hello world';
}
... returns a Promise. So you can execute it like one, .then()
and all.
test().then(message => {
// message = 'hello world'
});
So...
async function test() {
const user = await getUser();
const report = await user.getReport();
report.read = true
return report;
}
Is roughly analogous to...
function test() {
return getUser().then(function (user) {
return user.getReport().then(function (report) {
report.read = true;
return report;
});
});
}
In both cases, the callback passed to test().then()
will receive report
as its first parameter.
Generators (i.e. marking a function *
and using the yield
keyword) are a different concept altogether. They don't use Promises. They effectively allow you to 'jump' between different portions of your code, yielding a result from inside of a function and then jumping back to that point and resuming for the next yield block.
Although they feel somewhat similar (i.e. 'halting' execution until something happens somewhere else), async/await
only gives you that illusion because it messes with the internal ordering of Promise execution. It's not actually waiting - it's just shuffling when the callbacks happen.
Generators, by contrast, are implemented differently so that the generator can maintain state and be iterated over. Again, nothing to do with Promises.
The line is further blurred because at the current time of writing, support for async/await is scare; Chakracore supports it natively, and V8 has it coming soon. In the meantime, transpilers like Babel allow you to write async/await
and convert the code to generators. It's a mistake to conclude that generators and async/await are therefore the same; they're not... it just so happens that you can bastardize how yield
works alongside Promises to get a similar result.
Update: November 2017
Node LTS now has native async/await
support, so you ought never to need to use generators to simulate Promises.
Solution 3:
These answers all give valid arguments for why the async keyword is a good thing, but none of them actually mentions the real reason why it had to be added to the spec.
The reason is that this was a valid JS pre-ES7
function await(x) {
return 'awaiting ' + x
}
function foo() {
return(await(42))
}
According to your logic, would foo()
return Promise{42}
or "awaiting 42"
? (returning a Promise would break backward compatibility)
So the answer is: await
is a regular identifier and it's only treated as a keyword inside async functions, so they have to be marked in some way.
Fun fact: the original spec proposed more lightweight function^ foo() {}
for async syntax.
Solution 4:
The reason for the async keyword in front is simple so you know that return value will be transformed into a promise. If no keyword how would the Interpreter know to do this. I think this was first introduce in C# and EcmaScript is taking a loot of stuff from TypeScript. TypeScript and C# are conceived by Anders Hejlsberg and are similar. lets say you have a function (this one is just to have some asynchronous work)
function timeoutPromise() {
return (new Promise(function(resolve, reject) {
var random = Math.random()*1000;
setTimeout(
function() {
resolve(random);
}, random);
}));
}
this function will make us wait for a random time and return a Promise (if you use jQuery Promise is similar to Deferred) object. To use this function today you would write something like this
function test(){
timeoutPromise().then(function(waited){
console.log('I waited' + waited);
});
}
And this is fine. Now lets try to return the log message
function test(){
return timeoutPromise().then(function(waited){
var message = 'I waited' + waited;
console.log(message);
return message; //this is where jQuery Deferred is different then a Promise and better in my opinion
});
}
Ok this is not bad but there are two return statements and a function in the code.
Now with async this will look like this
async function test(){
var message = 'I waited' + (await timeoutPromise());
console.log(message);
return message;
}
The code is short and inline. If you written a lot of .then() or . done() you know how unreadable the code can get.
Now why the async keyword in front of function. Well this is to indicate that your return value is not what is returned. In theory you could write this(This can be done in c# I don't know if js will allow since it's not done).
async function test(wait){
if(wait == true){
return await timeoutPromise();
}
return 5;
}
you see, you return a number but the actual return will be a Promise you don't have to use
return new Promise(function(resolve, reject) { resolve(5);};
Since you can't await a number only a Promise await test(false)
will throw an exception and await test(true)
will not if you don't indicate async in front.