ES2017 - Async vs. Yield
Asked Answered
M

4

34

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.

Masker answered 17/7, 2015 at 19:28 Comment(6)
If you were to block that function, it's not just this function, but the function calling this one, and so on up the call stack, blocking, which is a massive departure from how JS currently works. Functions marked as async return promises for values to avoid this, as the caller would continue executing and eventually get the result from the promise result.Hedonic
Not sure whether your confusion is also about the difference between async and generator functions?Christianson
@Bergi: Yes, I think I hadn't got the idea what async will create in the object tree. Generators I know well. Async was my confusion because it looks form me there is no reason to make a difference (see while-loop). But thanks for your answer below - I commented also ...Masker
@loganfsmyth: Yes, that makes sense. The while-loop would block everything (because of LIFO) outside the event-loop. The async would be organized within the event-loop (what is FIFO)?Masker
@Danny: Do you know how promises work? If not, you should probably learn about them first before trying to understand async-await syntax.Christianson
@Bergi: yes I know - I made myself totally confused by trying to understand internal things (what happens under the hood). The initial question was about the need of the async word. Now I know it is about what type of object (including its methods) is created.Masker
C
22

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.

Christianson answered 17/7, 2015 at 21:59 Comment(6)
thanks - finally the reason is: It leads the created object. A generator-object would have value and done, a function simple the function-object and the async function will create a done, no value but a await-array. The JS engine will work through and note this type of flags by flow control ?Masker
Um, no. A generator object will have next, return and throw methods (which return .done/.value pairs), a promise will have a then method (which calls back with one of two values). Not sure what you mean by "flags" or an "await-array". The main difference is that generator code resumes from yield when it is called from outside (if at all, and pretty arbitrary), and async code resumes from await when the currently awaited promise resolves.Christianson
Is there any better way to track states than console.log(object) ? I Tried to understand this by creating some sequences, but the referenced object is empty. In the case of generators I get {value: "foo", done: false} if I console.log(generator.next()); - but I like to see the hole generator to understand what happens under the hood.Masker
You might want to do console.log(generator) and also put logs in the generator function, between the yields. I don't think there's a way to inspect the current state of the generator, though.Christianson
I tried both before. var gen =function* generator(){ console.log(gen);yield ...}; but as you said, it doesn't help.Masker
Don't confuse the generator function for the generator instance itself. I think davidwalsh.name/es6-generators explains it quite well.Christianson
B
20

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.

Bradawl answered 8/9, 2016 at 6:40 Comment(4)
very diligent answerSalmagundi
You really cleared that up for me. co uses generators and now I understand, async/await were just not available when it was made (unless I got it wrong).Gloomy
I was reading this blog post: blog.jscrambler.com/introduction-to-koajs (about the koa.js library) and I was wondering why the examples make an extensive use of generators and don't use async/await (which confused me about the nature of both - I'm new to them). Your answer seems to suggest that the syntax used in the post should be considered "outdated" (if async/await is supported).Beebe
Yeah, it is - that's Koa v1. Koa 2 uses Promises.Bradawl
H
11

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.

Hideandseek answered 19/1, 2017 at 14:25 Comment(5)
await(42) could simply have been invalid ES7. We could have been forced to use it without parenthesis, as in await 42, which is what everybody does in practice anyways. It is a real bummer to be forced to mark every function with async once you fall into the trap, and another one to not be able to use await in regular functions returning promises. It could have been much better than this...Aromatize
Maybe await needs to accept the parenthesis syntax in order to allow self executing functions await (function(){return promisify();})();Ithaca
Sure, the keyword is needed to achieve backwards compatibility*, but async is not just a keyword that creates a new scope to freely use await, it changes the fundamental way in which the function is executed, it has many probably unintended side effects.Vital
* but really, if we calculated how many symbols named await there are in legacy projects and divide it by some measure, lets say KLOC, would it be a really significant maintenance issue?Vital
This answer has been cited here.Kanchenjunga
M
3

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.

Mopup answered 9/6, 2016 at 0:50 Comment(6)
Your first test function seems to be missing the return? And I don't get how jQuery deferreds are better than proper promises, they work quite the same for return values.Christianson
timeoutPromise returns (new Promise). And I don't get how jQuery deferreds are betterMopup
Your first test function seems to be missing the return? timeoutPromise returns (new Promise). And I don't get how jQuery deferreds are better? you have more functions like allways,done,progress. linkMopup
timeoutPromise returns something, yes, but the first implementation of test does not. Regarding deferreds, jQuery is horrible and not standard-conforming, always is rather trivial, done is useless and progress is deprecated in all major libraries for good reason.Christianson
@Christianson jQuery 3.0 fixed their promises and made them conform to the Promises/A+ spec.Kennie
@Kennie Good for those who already use jQuery 3… but their API is still worse than even simple ES6 implementations.Christianson

© 2022 - 2024 — McMap. All rights reserved.