异步编程是现代 JavaScript 开发中一个重要方面,它使我们能够处理耗时的操作,而不会阻塞其他任务的执行。使用异步函数时,我们会遇到三个重要的关键字:await
、return
、return await
。在本文中,我们将探讨这些关键字之间的差异,并讨论何时使用每个关键字。
在深入探讨细节之前,让我们先阐明一下异步函数的用途。异步函数是一种特殊类型的函数,可以使用 await
关键字。它允许我们以更加同步和可读的方式编写异步代码,从而更容易处理 Promise 和执行非阻塞操作。当调用异步函数时,它会返回一个 Promise,该 Promise 解析为函数的最终结果。
现在,让我们探讨一下 await
、return
、 和 return await
在异步函数上下文中的差异。
让我们从这个异步函数开始:
代码语言:javascript复制async function waitAndMaybeReject() {
// 等待 1s
await new Promise(r => setTimeout(r, 1000));
// 掷硬币
const isHeads = Boolean(Math.round(Math.random()));
if(isHeads) return 'yay';
throw Error('Boo!');
}
它会返回一个等待一秒的 Promise,然后 50% 的几率以 "yay"
表示,或以错误拒绝,让我们以几种微妙的方式来使用它。
只是调用
让我们先来看一下,当我们简单地调用另一个异步函数而不正确处理返回的 Promise 时,异步函数的行为。请看下面的示例:
代码语言:javascript复制async function foo() {
try {
waitAndMaybeReject();
} catch(e) {
return 'caught';
}
}
在这里,如果直接调用 foo
,异步函数 foo
返回的 Promise 将始终以 undefined
表示,而无需等待函数 waitAndMaybeReject
。
因为我们没有 await
或者 return
异步函数 waitAndMaybeReject()
的结果,因此我们对它没有作出任何反应,像这样的代码通常都是错误的。
Await
关键字 await
在异步代码中起着至关重要的作用,它允许我们暂停异步函数的执行,直到承诺得到解决或拒绝,让我们看看它与仅调用 async 函数有何不同。
await
的本质:
- • 异步代码同步:
await
通过阻塞执行,直到等待的 Promise 被解析或拒绝,简化了异步代码的使用。 - • 增强的可读性:它消除了深度嵌套回调或
then()
长链的需要,从而极大地提高了代码的可读性。
async function foo() {
try {
await waitAndMaybeReject();
} catch(e) {
return 'caught';
}
}
在这里,如果调用 foo
,返回的 Promise 总是会等待一秒,然后以 undefined
或以 "caught"
表示 fulfill。
因为我们 await waitAndMaybeReject()
的结果,所以它 rejection 时,将变成错误抛出,我们的 catch 代码块也将执行。但是,如果 waitAndMaybeReject()
执行完毕,我们不会对值做任何处理。
Return
代码语言:javascript复制async function foo() {
try {
return waitAndMaybeReject();
} catch(e) {
return 'caught';
}
}
在这里,如果你调用 foo
,返回的 Promise 将始终等待一秒,然后要么以 "yay"
表示 fulfill,要么以 Error('Boo!')
表示 reject。
由于通过 return waitAndMaybeReject
,我们延迟了其结果,因此我们的 catch 代码块永远不会运行。
Return await
在 try/catch
块中,你需要的是 return await
。
retrun await
的本质:
- • 一致的值:
return await
可确保函数始终一致的返回 Promise 的解析值,即使在没有严格必要的情况下也是如此,从而确保返回数据类型的一致性。 - • 控制流清晰:在有条件逻辑的情况下,
return await
可以提供更清晰的控制流,从而更容易跟踪代码的执行路径。
async function foo() {
try {
return await waitAndMaybeReject();
} catch(e) {
return 'caught';
}
}
在这里,如果调用 foo
,将始终等待一秒后返回 Promise,然后以 "yay"
或者以 "caught"
表示 fulfill。
因为我们等待 waitAndMaybeReject()
的结果,所以它的 rejection 将变成抛出的 throw,我们的 catch 代码块将执行。如果 waitAndMaybeReject()
执行完毕,我们将返回其结果。
如果上述内容看起来令人困惑,那么将其视为两个独立的步骤可能会更容易理解:
代码语言:javascript复制async function foo() {
try {
// 等待 waitAndMaybeReject() 的结果结算,
// 并将已完成的值分配给 fulfilledValue:
const fulfillValue = await waitAndMaybeReject();
// 如果 waitAndMaybeReject() 的结果被拒绝,我们的代码就会抛出,然后跳到 catch 块。
// 否则,此块将继续运行:
return fulfillValue;
} catch(e) {
return 'caught';
}
}
注意:在 try/catch 块之外,return await
是多余的,ESLint 甚至有一条规则来检测它,但它允许在 try/catch 中使用。
参考:
- • https://jakearchibald.com/2017/await-vs-return-vs-return-await/
- • https://levelup.gitconnected.com/understanding-the-crucial-difference-await-vs-return-await-ea5827fe9f03