“返回等待承诺”和“返回承诺”之间的区别

2022-08-30 01:48:35

给定下面的代码示例,行为上是否存在任何差异,如果是,这些差异是什么?

return await promise

async function delay1Second() {
  return (await delay(1000));
}

return promise

async function delay1Second() {
  return delay(1000);
}

据我所知,第一个函数将在异步函数中进行错误处理,并且错误将从异步函数的 Promise 中冒出。但是,第二个需要少一个刻度。这是正确的吗?

此代码段只是返回 Promise 以供参考的常用函数。

function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

答案 1

大多数情况下,和 之间没有可观察到的差异。的两个版本具有完全相同的可观察行为(但根据实现的不同,该版本可能会使用稍微多一些的内存,因为可能会创建中间对象)。returnreturn awaitdelay1Secondreturn awaitPromise

但是,正如@PitaJ所指出的,有一种情况存在差异:如果 or 嵌套在 - 块中。请考虑此示例returnreturn awaittrycatch

async function rejectionWithReturnAwait () {
  try {
    return await Promise.reject(new Error())
  } catch (e) {
    return 'Saved!'
  }
}

async function rejectionWithReturn () {
  try {
    return Promise.reject(new Error())
  } catch (e) {
    return 'Saved!'
  }
}

在第一个版本中,async 函数在返回其结果之前等待被拒绝的承诺,这会导致拒绝变成异常并达到子句;因此,该函数将返回解析为字符串“已保存!”的承诺。catch

但是,该函数的第二个版本确实直接返回被拒绝的 promise,而无需在异步函数中等待它,这意味着不会调用该情况,而是调用方获得拒绝。catch


答案 2

正如其他答案所提到的,当通过直接返回承诺来让承诺冒泡时,可能会有轻微的性能优势 - 仅仅是因为你不必先等待结果,然后再用另一个承诺来包装它。但是,还没有人谈论尾部调用优化

尾部调用优化“正确的尾部调用”是解释器用于优化调用堆栈的一种技术。目前,还没有多少运行时支持它 - 即使它在技术上是ES6标准的一部分 - 但是将来可能会添加支持,所以你可以通过在现在编写好的代码来为此做好准备。

简而言之,TCO(或 PTC)通过为由另一个函数直接返回的函数打开新帧来优化调用堆栈。相反,它重用相同的帧。

async function delay1Second() {
  return delay(1000);
}

由于 由 直接返回,支持 PTC 的运行时将首先为(外部函数)打开一个帧,但随后它不会为(内部函数)打开另一个帧,而是重用为外部函数打开的同一帧。这优化了堆栈,因为它可以防止具有非常大的递归函数的堆栈溢出(嘿嘿),例如。从本质上讲,它变成了一个循环,这要快得多。delay()delay1Second()delay1Second()delay()fibonacci(5e+25)

仅当直接返回内部函数时,才会启用 PTC。当函数的结果在返回之前被更改时,不会使用它,例如,如果您有 ,或 .return (delay(1000) || null)return await delay(1000)

但就像我说的,大多数运行时和浏览器还不支持PTC,所以它现在可能不会产生巨大的影响,但它不会伤害你的代码未来。

在以下问题中阅读更多内容:Node.js:异步函数中的尾部调用是否有优化?