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.

Felix Kling
  • 705,106
  • 160
  • 1,004
  • 1,072
Danny
  • 1,018
  • 7
  • 22
  • 2
    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. – loganfsmyth Jul 17 '15 at 19:44
  • 2
    Not sure whether your confusion is also about the difference between `async` and generator functions? – Bergi Jul 18 '15 at 09:21
  • 1
    @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 ... – Danny Jul 18 '15 at 13:27
  • 1
    @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)? – Danny Jul 18 '15 at 13:32
  • @Danny: Do you know how promises work? If not, you should probably learn about them first before trying to understand `async`-`await` syntax. – Bergi Jul 18 '15 at 13:53
  • 1
    @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. – Danny Jul 18 '15 at 15:52

4 Answers4

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.

Community
  • 1
  • 1
Bergi
  • 513,640
  • 108
  • 821
  • 1,164
  • 1
    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 ? – Danny Jul 18 '15 at 13:24
  • 1
    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. – Bergi Jul 18 '15 at 13:52
  • 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. – Danny Jul 18 '15 at 16:04
  • You might want to do `console.log(generator)` and also put logs *in* the generator function, between the `yield`s. I don't think there's a way to inspect the current state of the generator, though. – Bergi Jul 18 '15 at 16:54
  • I tried both before. `var gen =function* generator(){ console.log(gen);yield ...};` but as you said, it doesn't help. – Danny Jul 18 '15 at 17:15
  • Don't confuse the generator function for the generator instance itself. I think http://davidwalsh.name/es6-generators explains it quite well. – Bergi Jul 18 '15 at 17:36
17

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.

Lee Benson
  • 9,401
  • 5
  • 40
  • 53
  • 2
    very diligent answer – prosti Dec 02 '16 at 00:48
  • 1
    You really cleared that up for me. [`co`](https://github.com/tj/co) uses generators and now I understand, `async`/`await` were just not available when it was made (unless I got it wrong). – Camilo Martin Aug 13 '17 at 21:13
  • I was reading this blog post: https://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). – Nicolas Le Thierry d'Ennequin Nov 07 '17 at 10:45
  • 1
    Yeah, it is - that's Koa v1. Koa 2 uses Promises. – Lee Benson Nov 07 '17 at 10:59
10

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.

phaux
  • 529
  • 5
  • 8
  • `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... – DanielM Oct 06 '18 at 19:27
  • Maybe await needs to accept the parenthesis syntax in order to allow self executing functions `await (function(){return promisify();})();` – Kernel James Apr 07 '21 at 15:09
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.

Flimm
  • 97,949
  • 30
  • 201
  • 217
Filip Cordas
  • 2,322
  • 1
  • 12
  • 22
  • 1
    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. – Bergi Jun 09 '16 at 01:33
  • timeoutPromise returns (new Promise). And I don't get how jQuery deferreds are better – Filip Cordas Jun 10 '16 at 15:03
  • 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. [link](http://stackoverflow.com/questions/5436327/jquery-deferreds-and-promises-then-vs-done) – Filip Cordas Jun 10 '16 at 15:13
  • `timeoutPromise` returns something, yes, but the first implementation of `test` does not. Regarding deferreds, [jQuery is horrible and not standard-conforming](http://stackoverflow.com/q/23744612/1048572), `always` is [rather trivial](http://stackoverflow.com/a/32362233/1048572), `done` is useless and `progress` is deprecated in all major libraries for good reason. – Bergi Jun 10 '16 at 16:34
  • @Bergi jQuery 3.0 fixed their promises and made them conform to the Promises/A+ spec. – Flimm Aug 23 '16 at 18:04
  • @Flimm Good for those who already use jQuery 3… but their API is still worse than even simple ES6 implementations. – Bergi Aug 23 '16 at 18:08