知乎回答备份

为什么使用了 await 语法的函数必须用 async 标记

Jan 9, 2020

原知乎回答链接

原问题:

有个疑惑,为什么在 JavaScript 里,如果在某个函数里使用了 await 语法,则这个函数必须定义成 async 的? 如下代码:

// 函数 a 的定义
async function a(){
  await b()
}
await a(); // 函数 a 的调用

从逻辑上来讲,如果函数 a 不定义成 async,执行器在调用 a 的时候通过是否加 await 来区别,好像也可以达到同样的效果。

以下是回答备份:


首先 async/await 和 Generator 的思路是一脉相承的 —— async/await 提案最早的草稿里就说了,它相当于一个带自动 spawn 功能的 Generator,就是一个语法糖。1

所以,它带上特殊标记(async)的原因应该和 Generator 需要用 * 标记的原因类似。

然后我们需要理解为什么 Generator 不能直接用 function 而是用了一个 function * 的怪异语法。

追查 Generator 语法的最早版本实现我们可以发现,Mozilla JS 1.7 里的 Generator 是不需要特殊标记的,它就是一个包含了 yield 语句的特殊函数。

但是 Mozilla 的人计划把这个语法特性标准化时有了不同的考虑(以下原因排名分先后):

  1. yield 只有在严格模式下才是一个保留字,在非严格模式下它可以被当作普通的变量名使用,这样给解析造成了困难。2 而标准委员会的人不希望限制新特性的使用场景,所以他们必须选择一种向后兼容的语法。 3
  2. 从可读性角度考虑,如果函数体很长,而 yield 出现在很后面,读者需要很仔细地阅读才能意识到这个不是普通函数,不利于快速理解代码。 async/await 同理:await 在非严格模式(sloppy mode)下不是保留字4,以及可读性考虑。

现在随着现代前端代码逐渐迁移到了 ES Module(默认是严格模式),标准委员会也在计划扩大 await 的使用场景,所以有了 top-level await 提案 。在这个提案里,只要是在 Module 环境下,我们是可以直接写 await 的:

// x.mjs
console.log("X1");
await new Promise(r => setTimeout(r, 1000));
console.log("X2");

但是在这种场景使用 await 语法,需要考虑的不仅仅在于词法解析、语法设计等等,而是有很多实现细节上的小坑:比如潜在的死锁问题、可能模块加载时间受影响、和 CommonJS 模块的兼容等等……

所以当初标准委员会才决定把 top-level awaitawait/async 提案中摘出来,单独标准化,这过程中还涉及到和各个执行环境(loader)的协作。后续标准讨论过程中还引发了一次比较大的讨论:Top-level await is a footgun。现在其中大部分问题都有了一定共识,提案也进展到了 Stage 3,非常建议读一下提案的 FAQ 部分。不过最后进入标准还是有困难,作为 TC39 成员代表的贺老已经表达了强烈反对(参见此答案的评论部分)……

从这个提案的经历,我们也可以得出本问题的另一个解答:相比在任意情况下都允许 await,把它限制在 async function {} 里,实现起来实在是简单得多了。

出于实用主义的考虑,先把限制加上去,功能实现出来,应该更符合大多数 JavaScript 开发者的利益吧。

Footnotes

  1. 最初设想里,async function <name>?<argumentlist><body> 等价于 function <name>?<argumentlist>{ return spawn(function*() <body>); }

  2. 以下代码在非严格模式下是合法的:function f() { var yield = 1; return yield; }

  3. 一个当时很多人持有的观点:ES6 doesn’t need opt-in
    现在回过头来看,"use strict"script type="module" 这种形式的 opt-in 确实很影响用户接受度(要不是 babel / webpack 的流行,这两种特性使用情况肯定比现在惨淡得多),所以标准委员会整体倾向于 ES6 新特性尽可能兼容旧代码。

  4. 最最早版本的 async function 草案是这个:[[strawman:deferred_functions]],其中提到了语法上的考虑。

Some rights reserved
Except where otherwise noted, content on this page is licensed under a Creative Commons Attribution-ShareAlike 4.0 International license.