描述
async function 声明创建一个 AsyncFunction 对象。每次调用异步函数时,都会返回一个新的 Promise 对象,该对象将会被解决为异步函数的返回值,或者被拒绝为异步函数中未捕获的异常。
异步函数可以包含零个或者多个 await 表达式。await 表达式通过暂停执行使返回 promise 的函数表现得像同步函数一样,直到返回的 promise 被兑现或拒绝。返回的 promise 的解决值会被当作该 await 表达式的返回值。使用 async/await 关键字就可以使用普通的 try/catch 代码块捕获异步代码中的错误。
备注:await 关键字只在常规 JavaScript 代码中的异步函数内有效。如果你在异步函数体之外使用它,则会抛出 SyntaxError。
await 可以单独与 JavaScript 模块一起使用。
备注:async/await 的目的在于简化使用基于 promise 的 API 时所需的语法。async/await 的行为就好像搭配使用了生成器和 promise。
异步函数总是返回一个 promise。如果一个异步函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。
例如,请看以下代码:
jsasync function foo() {
return 1;
}
类似于:
jsfunction foo() {
return Promise.resolve(1);
}
备注:
即使异步函数的返回值看起来像是被包装在了一个 Promise.resolve 中,但它们不是等价的。
如果给定的值是一个 promise,异步函数会返回一个不同的引用,而 Promise.resolve 会返回相同的引用,
当你想要检查一个 promise 和一个异步函数的返回值是否等价时,这可能是一个麻烦。
jsconst p = new Promise((res, rej) => {
res(1);
});
async function asyncReturn() {
return p;
}
function basicReturn() {
return Promise.resolve(p);
}
console.log(p === basicReturn()); // true
console.log(p === asyncReturn()); // false
异步函数的函数体可以被看作是由零个或者多个 await 表达式分割开来的。从顶层代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行的。因此,不包含 await 表达式的异步函数是同步运行的。然而,如果函数体内包含 await 表达式,则异步函数就一定会异步完成。
例如:
jsasync function foo() {
await 1;
}
等价于:
jsfunction foo() {
return Promise.resolve(1).then(() => undefined);
}
每个 await 表达式之后的代码可以被认为存在于 .then 回调中。通过这种方式,可以通过函数的每个可重入步骤来逐步构建 promise 链。而返回值构成了链中的最后一个环。
在接下来的示例中,我们依次 await 两个 promise,整个 foo 函数的执行将会被分为三个阶段。
foo 函数的第一行将会同步执行,其中 await 配置了待定的 promise。然后 foo 的进程将被暂停,并将控制权交还给调用 foo 的函数。
一段时间后,当第一个 promise 被兑现或拒绝的时候,控制权将重新回到 foo 内。第一个 promise 的兑现结果(如果没有被拒绝的话)将作为 await 表达式的返回值。在这里 1 被赋值给 result1。程序继续执行,并计算第二个 await 表达式。同样的,foo 的进程将被暂停,并交出控制权。
一段时间后,当第二个 promise 被兑现或拒绝的时候,控制权将重新回到 foo。第二个 promise 的兑现结果将作为第二个 await 表达式的返回值。在这里 2 被赋值给 result2。程序继续执行到返回表达式(如果有的话)。默认的返回值 undefined 将作为当前 promise 的兑现值被返回。
jsasync function foo() {
const result1 = await new Promise((resolve) =>
setTimeout(() => resolve("1")),
);
const result2 = await new Promise((resolve) =>
setTimeout(() => resolve("2")),
);
}
foo();
注意:promise 链不是一次就构建好的,相反,promise 链是随着控制权依次在异步函数中交出并返回而分阶段构建的。因此在处理并发异步操作时,我们必须小心错误处理。
例如,在下面代码中,即使在 promise 链中进一步配置了 .catch 处理方法,也会抛出一个未处理的 promise 被拒绝的错误。这是因为 p2 直到控制从 p1 返回后才会连接到 promise 链。
jsasync function foo() {
const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000));
const p2 = new Promise((_, reject) => setTimeout(() => reject("2"), 500));
const results = [await p1, await p2]; // 不要这么写!请使用 Promise.all 或者 Promise.allSettled。
}
foo().catch(() => {}); // 尝试捕捉所有的错误...
async function 声明的行为类似于 function 声明,它会被提升到其作用域的顶部,并且可以在其作用域的任何位置被调用,并且只能在其他上下文中被重新声明。