"返回等待"是否存在性能问题?

sfl*_*che 10 javascript async-await

我看到有一个eslint规则,no-return-await禁止return await.

在规则的描述中,它表示return await添加"extra time before the overarching Promise resolves or rejects".

但是,当我查看MDN async函数文档时,"简单示例"显示了一个示例,其中return await不包含任何可能导致性能问题的描述.

return await一个实际的性能问题,因为eslint文档建议?

如果是这样,怎么样?

Ber*_*rgi 24

不,没有任何性能问题.这只是一个不必要的额外操作.执行可能需要更长的时间,但应该难以察觉.它类似于整数return x+0而不是return x整数x.或者说,完全等同于毫无意义.then(x => x).

它并没有造成实际的伤害,但我认为这是一种不好的风格,并且表明作者没有完全理解承诺和async/ await.

但是,有一种情况会产生重大影响:

try {
    …
    return await …;
} …
Run Code Online (Sandbox Code Playgroud)

await确实抛弃拒绝,并且在任何情况下等待承诺解决catchfinally执行处理程序.平原return会忽略这一点.

  • FWIW,截至 ES2020 的一项更改,假设正在等待的承诺 (`x`) 是本机承诺(不是第 3 方 thenable),则 `return wait x;` 和 `return x;` 位于 `try`/` 之外catch` 现在的处理方式完全相同。没有额外的异步周期。 (3认同)
  • @TJCrowder https://github.com/tc39/ecma262/issues/2770 :-) (2认同)

Sho*_*use 24

我添加答案是因为评论太长。async我最初对如何工作和工作有一个非常长、详细的解释await。但它是如此复杂,实际数据可能更容易理解。所以这是呃,简化的解释。注意:这在 Chrome v97、FireFox v95 和 Node v16 上运行,结果相同。

关于什么更快的答案:这取决于您返回的内容以及您如何调用它。工作原理与它运行PromiseResolveawait不同(类似于但它是内部的)。基本上,如果你运行一个 Promise(一个真正的 Promise,而不是一个 polyfill),就不要尝试包装它。它按原样执行承诺。这会跳过一个刻度。这是 2018 年以来的“较新”更改。总之,评估始终返回 Promise 的结果,而不是 Promise,同时尽可能避免包装 Promise。这意味着总是至少需要一个刻度。asyncPromise.resolveawaitawaitawaitawait

但这实际上awaitasync没有使用这个旁路。现在async使用良好的PromiseCapability Record。我们关心这如何解决承诺。关键点是,如果决议是“不是Object”或.then不是,它将立即开始履行Callable。如果两者都不成立,(您返回 a Promise),它将执行 aHostMakeJobCallback并附加到thenPromise 中,这基本上意味着我们要添加一个勾号。澄清一下,如果你在函数中返回 Promise async,它会添加一个额外的勾号,但如果你返回 Non-Promise,则不会。

因此,在所有前言(这是简化版本)中,以下是您的图表,显示了await foo()调用返回之前有多少个刻度:

无承诺 承诺
() => 结果 1 1
异步()=>结果 1 3
async () => 等待结果 2 2

这是用 测试的await foo()。您也可以使用 进行测试foo().then(...),但刻度是相同的。(如果你不使用 an await,那么同步函数确实会是 0。虽然foo().then会崩溃,所以我们需要一些真实的东西来测试。)这意味着我们的楼层是 1。

如果您理解我上面的解释(希望如此),那么这是有道理的。同步函数是有意义的,因为我们在函数中的任何时刻都不会调用暂停执行:await foo()将需要 1 个时钟周期。

async喜欢“无承诺”并期待它们。如果找到,它会立即返回。但如果它找到一个 Promise,它就会附加到该 Promise 的then. 这意味着它将执行 Promise (+1),然后等待then完成(另一个 +1)。这就是为什么它是 3 个刻度。

await将把 a 转换为完美的Promisea 。如果您在 a 上调​​用await ,它将执行它而不添加任何额外的刻度(+1)。但是,会将 a 转换为 a然后运行它。这意味着无论你反对什么,总是需要打勾。Non-PromiseasyncPromiseawaitNon-PromisePromiseawait

因此,总而言之,如果您想要最快的执行速度,您需要确保您的async函数始终包含至少一个await. 如果没有,那么只需使其同步即可。您始终可以调用await任何同步函数。现在,如果您想真正调整性能,并且要使用async,则必须确保始终返回 Non-Promise,而不是Promise。如果您要返回 a Promise,请先使用 进行转换await。也就是说,你可以像这样混合搭配:


async function getData(id) {
  const cache = myCacheMap.get(id);
  if (cache) return cache; // NonPromise returns immediately (1 tick)
  
  // return fetch(id); // Bad: Promise returned in async (3 ticks)
  return await fetch(id); // Good: Promise to NonPromise via await (2 ticks)
}
Run Code Online (Sandbox Code Playgroud)

考虑到这一点,我有一堆代码需要重写:)


参考:

https://v8.dev/blog/fast-async

https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-async-functions-abstract-operations-async-function-start


测试:

async function test(name, fn) {
  let tick = 0;
  const tock = () => tick++;
  Promise.resolve().then(tock).then(tock).then(tock);

  const p = await fn();
  console.assert(p === 42);
  console.log(name, tick);
}

await Promise.all([
  test('nonpromise-sync', () => 42),
  test('nonpromise-async', async () => 42),
  test('nonpromise-async-await', async () => await 42),
  test('promise-sync', () => Promise.resolve(42)),
  test('promise-async', async () => Promise.resolve(42)),
  test('promise-async-await', async () => await Promise.resolve(42)),
]);

setTimeout(() => {}, 100);
Run Code Online (Sandbox Code Playgroud)