ES2017 - Async vs. Yield

我的梦境 提交于 2019-12-28 12:13:31

问题


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.


回答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.




回答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.




回答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.




回答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.



来源:https://stackoverflow.com/questions/31483342/es2017-async-vs-yield

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!