ES2017 - 异步与收益

ES2017 - Async vs. Yield

我对当前关于将异步函数和关键字 await 添加到下一个 EcmaScript 的讨论感到困惑。

我不明白为什么在function关键字之前必须有async关键字。

从我的角度来看,await 关键字等待生成器或承诺 完成 的结果,一个函数的 return 应该足够了。

await 应该在正常函数和生成器函数中简单可用,无需额外的 async 标记。

如果我需要创建一个函数,作为 await 的结果,我只需使用一个 promise。

我问的原因是this很好的解释,下面的例子来自:

async function setupNewUser(name) {  
  var invitations,
      newUser = await createUser(name),
      friends = await getFacebookFriends(name);

  if (friends) {
    invitations = await inviteFacebookFriends(friends);
  }

  // some more logic
}

也可以像普通函数一样完成,如果一个函数的执行会等待完成洞函数,直到所有等待都完成。

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.

}

在我看来,整个函数的执行一直持续到下一个滴答(等待实现)完成。与 Generator-Function 的区别在于 next() 触发并更改对象的值和完成字段。相反,函数将在完成时简单地返回结果,触发器是一个函数内部触发器,如 while 循环。

I do not understand why it is necessary to have the async keyword before the function keyword.

出于与我们在生成器函数之前有 * 符号相同的原因:它们将函数标记为非凡的。它们在这方面非常相似 - 它们添加了一个视觉标记,表明此函数的主体本身不会 运行 完成,但可以与其他代码任意交错。

  • * 表示一个生成器函数,它将始终 return 一个生成器,可以通过类似于迭代器的方式从外部使用它来推进(和停止)。
  • async 表示一个异步函数,它总是 return 一个依赖于其他承诺的承诺,并且其执行与其他异步操作并发(并且可能从外部取消)。

的确,关键字不是绝对必要的,函数的类型可以根据相应的关键字(yield(*)/await)是否出现在其主体中来确定,但这会导致难以维护的代码:

  • 不太好理解,因为需要扫描全身才能确定种类
  • 更容易出错,因为很容易通过 adding/removing 那些关键字破坏函数而不会出现语法错误

a normal function, whose execution will wait for finishing the hole body until all awaits are fulfilled

听起来你想要一个阻塞函数,它是 very bad idea 在并发设置中。

前面的 async 关键字的原因很简单,所以你知道 return 值将被转换为一个承诺。如果没有关键字,解释器怎么知道要这样做。 我认为这是在 C# 中首次引入的,而 EcmaScript 正在从 TypeScript 中抢走一些东西。 TypeScript 和 C# 是由 Anders Hejlsberg 构想的,有相似之处。 假设您有一个函数(这个函数只是为了进行一些异步工作)

 function timeoutPromise() {  
     return (new Promise(function(resolve, reject) {
         var random = Math.random()*1000;
         setTimeout(
             function() {
                 resolve(random);
             }, random);
     }));
 }

这个函数会让我们等待一个随机时间和return一个Promise(如果你使用jQueryPromise类似于Deferred)对象。今天要使用这个函数,你会写这样的东西

function test(){
    timeoutPromise().then(function(waited){
        console.log('I waited' + waited);
    });
}

这很好。现在让我们尝试 return 日志消息

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
    });
}

好的,这还不错,但是代码中有两个 return 语句和一个函数。

现在有了异步,这看起来像这样

  async function test(){
      var message = 'I waited' +  (await timeoutPromise());
      console.log(message);
      return message;
  }

代码简短且内联。如果你写了很多 .then() 或 . done() 你知道代码有多难读。

现在为什么要在函数前面加上 async 关键字。嗯,这表明您的 return 值不是 returned 的值。理论上你可以写这个(这可以在c#中完成我不知道js是否允许,因为它没有完成)。

 async function test(wait){
     if(wait == true){
         return await timeoutPromise();
     }
     return 5;                
 }

你看,你 return 一个数字,但实际 return 将是一个你不必使用的 Promise return new Promise(function(resolve, reject) { resolve(5);}; 因为你不能等待一个数字,只有 Promise await test(false) 会抛出异常,而 await test(true) 如果你不在前面指示异步则不会。

通过将函数标记为 async,您告诉 JS 始终 return Promise。

因为它总是 return 一个 Promise,它也可以 await 在它自己的块内的 promise。把它想象成一个巨大的 Promise 链——函数内部发生的事情被有效地固定到它的内部 .then() 块上,returned 是链中最后的 .then()

例如,这个函数...

async function test() {
  return 'hello world';
}

... return是一个承诺。所以你可以像一个一样执行它,.then() 和所有。

test().then(message => {
  // message = 'hello world'
});

所以...

async function test() {
  const user = await getUser();
  const report = await user.getReport();
  report.read = true
  return report;
}

大致类似于...

function test() {
  return getUser().then(function (user) {
    return user.getReport().then(function (report) {
      report.read = true;
      return report;
    });
  });
}

在这两种情况下,传递给 test().then() 的回调将接收 report 作为其第一个参数。

生成器(即标记函数 * 并使用 yield 关键字)完全是一个不同的概念。他们不使用承诺。它们有效地允许您在代码的不同部分之间 'jump',从函数内部产生结果,然后跳回到该点并继续下一个 yield 块。

虽然它们感觉有些相似(即 'halting' 执行直到其他地方发生某些事情),async/await 只会给你那种错觉,因为它 与 Promise 执行的内部顺序混淆。这不是 实际上 等待 - 它只是在回调发生时洗牌。

相比之下,生成器的实现方式不同,因此生成器可以保持状态并进行迭代。同样,与 Promises 无关。

这条线更加模糊,因为在撰写本文时,对 async/await 的支持还很可怕; Chakracore 原生支持它,V8 has it coming soon. In the meantime, transpilers like Babel allow you to write async/await and convert the code to generators。因此得出生成器和 async/await 相同的结论是错误的;他们不是......碰巧你可以混杂 yield 如何与 Promises 一起工作以获得类似的结果。

更新:2017 年 11 月

Node LTS 现在具有原生 async/await 支持,因此您永远不需要使用生成器来模拟 Promises。

这些答案都给出了为什么 async 关键字是一件好事的有效论据,但其中 none 实际上提到了 必须 添加它的真正原因符合规范。

原因是这是一个有效的 ES7 之前的 JS

function await(x) {
  return 'awaiting ' + x
}

function foo() {
  return(await(42))
}

按照你的逻辑,foo() return Promise{42} 还是 "awaiting 42"? (return使用 Promise 会破坏向后兼容性)

所以答案是:await是一个常规标识符,它只被视为异步函数中的关键字,因此必须以某种方式对其进行标记。

有趣的事实:原始规范提出了更轻量级的异步语法 function^ foo() {}