Why no-return-await vs const x = await?

74

9
你真的不应该使用第二个版本,因为它没有任何好处。正确的代码行应该是 return foo(); - Igor
2
那么为什么不说“应避免使用const x = await foo; return x”呢? - AturSams
1
@zehelvion - 可能是因为在转译时检测这个很难,但两个语句都是等价的,应该避免使用。 - Igor
1
请查看此处对no-return-await规则的解释。 - Bergi
2
@Igor return await 的优点是,已经在 V8 中启用了一个标志,您可以获得完整的异步堆栈跟踪。这是因为只要原始函数尚未完成,完整的堆栈仍然很容易重建。如果没有 await,如果直接返回 promise,则当调用堆栈中更深处的实际 promise 创建函数抛出异常时,该函数将永远消失。请查看 https://v8.dev/blog/fast-async 上的“零成本异步堆栈跟踪”。 - Mörre
显示剩余2条评论
6个回答

93

基本上是因为 return await 是多余的。

从稍微高一些的层次来看,你实际上如何使用一个 async 函数:

const myFunc = async () => {
  return await doSomething();
};

await myFunc();
任何async函数都将返回一个Promise,必须将其视为Promise处理(可以直接作为Promise处理,也可以同时使用await进行处理)。
如果在函数内部使用await,这是多余的,因为函数外部也会以某种方式await它,所以没有理由不仅仅发送Promise,让外部内容来处理它。
这不是语法错误或不正确的,通常也不会引起问题。这只是完全多余的,这就是为什么语法检查器会触发它的原因。

2
我不确定“将承诺发送并让外部处理它”是否完全正确。在我的测试中,通过直接返回承诺和等待然后返回值,都会创建一个新的承诺:function waitForN(time) { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('Rejected')); }, time); }); } async function testing() { myPromise = waitForN(1000); myPromise.catch(error => console.log(error)); return myPromise; }这将导致一个已捕获和一个未捕获的承诺。 - TigerBear
1
@samanime请看我在OP问题下的评论。自从V8引入了“零成本异步堆栈跟踪”以来,使用return await与返回Promise显着不同:您将获得一个堆栈跟踪(至少在现在的V8中是这样的)! - Mörre
1
@samanime 在我的测试中没有出现这种情况。我得到了一个堆栈跟踪,这正是他们所声称的。如果你没有得到他们所声称的结果,你应该提交一份报告。此外,要注意不要进行过于简单的测试,因为基于承诺和它们的“微任务”队列实现的行为如果不是真正异步的话,就不会表现出相同的行为。 - Mörre
1
@samanime 我是指我的先前评论...无论你正在测试什么,它不是那个功能。它是有效的,我尝试过- Google也试过。或者如果不是这样,那么在你的设置中可能有一个错误。找到它或报告它。 - Mörre
1
使用try{ return await foo() } finally { /* some code */ }确实有意义。如果去掉await,语义会变成"在获得承诺之后,无论发生什么都执行这个操作",而不是原本的意图,即"在承诺解决(完成或拒绝)之后执行这个操作"。很容易忽视这样的结构。 - programaths
显示剩余5条评论

57

使用return await在Node.js、Chrome和其他几个浏览器中使用的v8引擎中确实有一些新引入的好处:

v8引入了一个--async-stack-traces标志,从V8 v7.3开始默认启用(Node.js v12.0.0)。

此标志通过将异步函数调用堆栈跟踪丰富到错误堆栈属性中,提供了更好的开发人员体验。

async function foo() {
  return bar();
}

async function bar() {
  await Promise.resolve();
  throw new Error('BEEP BEEP');
}

foo().catch(error => console.log(error.stack));


Error: BEEP BEEP
    at bar (<anonymous>:7:9)

请注意,通过调用 return bar();foo() 函数调用不会出现在错误堆栈中。将其更改为 return await bar(); 会提供更好的错误堆栈输出:
async function foo() {
  return await bar();
}
foo();

Error: BEEP BEEP
    at bar (<anonymous>:7:9)
    at async foo (<anonymous>:2:10)

这确实提供了更好的错误跟踪,因此强烈鼓励您始终等待您的承诺。

此外,async/wait现在优于手写的promise代码:

 

async/await 现在优于手写的promise代码。关键要点是我们通过修补规范(而不仅仅是V8,而是所有JavaScript引擎)显着减少了异步函数的开销。来源

在v8.dev博客上阅读有关这些变更的更多信息:https://v8.dev/blog/fast-async#improved-developer-experience


2
如果是这样的话,为什么const x = await foo()return await foo()更好呢?这似乎没有解释清楚。 - AturSams
2
如果你只是写 const x = 然后返回它,它就能检测到,但我明白你的意思。这完全相同,而它不发出警告的原因是它不想费力去检测更复杂的用例。 - AturSams
6
如果return语句位于try ... catch块内,那么在异步函数中使用return await就不是无用的了。在这种情况下,使用await将允许您立即处理任何错误。 - John Weisz
3
如果您在当前版本的V8上运行代码(例如当前的Node.js),使用"return await"非常有用- 在返回之前等待可以让V8组合完整的异步堆栈跟踪(没有任何“成本”,因为由于await,调用函数的上下文是免费提供的)。 - Mörre
2
@Bamieh https://v8.dev/blog/fast-async -- 向下滚动到“零成本”部分;请注意,该功能现在已默认启用,例如在当前的node.js和Chrome版本中。 - Mörre
显示剩余6条评论

24

因为你只需要

async function() {
  return foo();
}

async function 的返回结果始终为 Promise,无论您在函数体内返回确切的值还是另一个 Promise 对象。


3
这是这里最好的答案!! - Yoni Rabinovitch
直接返回foo()而非return await foo() - jmazin
3
不再是这样了。如果你在当前版本的V8上运行代码(例如当前的node.js),"return await"非常有用-在返回之前等待让V8组合完整的异步堆栈跟踪(没有任何“成本”,因为由于await,调用函数的上下文是“免费”可用的)。 - Mörre
但是如果你稍后将返回值包装在try-catch中,你必须记得添加await关键字。如果跳过await,你就会牺牲正确性和可重构性。 - Tamas Hegedus

7
更新:no-return-await规则现已弃用。世界正在痊愈。
看起来很多人在评论中争论return await的有用性。让我添加一个演示,展示堆栈跟踪受到的影响。
在我看来,跳过await是一种误导性的“优化”尝试,只为了节省6个字符和一个微任务(编辑:ShortFuse刚刚证明在当前v8版本中实际上不是节省而是增加了一个微任务),却牺牲了堆栈跟踪、异常捕获、可重构性和代码一致性。简单来说,原则就是:如果你有一个异步调用,请使用await(除非你在做像并行处理或异步缓存这样的高级操作)。我相信我们应该坚持这个原则,并且始终使用return await,并关闭那个no-return-await的eslint规则。
小演示:

async function main() {
  console.log("\nStatcktrace with await shows testStackWithAwait:");
  await testStackWithAwait().catch(logStack);
  console.log("\nStatcktrace without await hides testStackWithoutAwait:");
  await testStackWithoutAwait().catch(logStack);
  console.log("\nFinally happens before try block ends without await:");
  await testFinallyWithoutAwait();
}

async function fnThatThrows() {
  await delay(1);
  throw new Error();
}

async function testStackWithoutAwait() {
  return fnThatThrows(); // bad
}

async function testStackWithAwait() {
  return await fnThatThrows(); // good
}

async function fnThatLogs() {
  await delay(1);
  console.log('inside');
}

async function testFinallyWithoutAwait() {
  try {
    return fnThatLogs(); // bad
  } finally {
    console.log('finally');
  }
}

function logStack(e) {
  console.log(e.stack);
}

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

main().catch(console.error);

在Windows上的Chrome 103版本中,我得到了以下日志:
Statcktrace with await shows testStackWithAwait:
Error
    at fnThatThrows (https://stacksnippets.net/js:23:9)
    at async testStackWithAwait (https://stacksnippets.net/js:31:10)
    at async main (https://stacksnippets.net/js:14:3)

Statcktrace without await hides testStackWithoutAwait:
Error
    at fnThatThrows (https://stacksnippets.net/js:23:9)
    at async main (https://stacksnippets.net/js:16:3)

Finally happens before try block ends without await:
finally
inside

3
返回 asyncFunc 和返回 await Promise.resolve() 的显著区别是,通过使用第二种方法,如果在异步函数内部出现问题,你可以捕获错误。
function afunction() {
  return asyncFun();
}
   
// with await
async function afunction() {
  try {
    return await asyncFun();
  } catch(err) {
    handleError(err);
    // return error result;
  }
}

   
    

-1

哦,我认为很容易理解,当我们等待特定值以继续进程时,我们使用“await”,如果进程完成(查看返回),我们就不需要“await”语法了。

return await foo(); //is redundant
return foo(); //is the correct way

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接