await
Baseline 广泛可用 *
语法
await expression
参数
表达式-
一个
Promise、一个 thenable 对象,或任何需要等待的值。
返回值
Promise 或 thenable 对象的完成值,或者,如果表达式不是 thenable,则为表达式本身的值。
异常
如果 Promise 或 thenable 对象被拒绝,则抛出拒绝原因。
描述
await 通常通过将 Promise 作为 expression 传递来解包 Promise。使用 await 会暂停其所在 async 函数的执行,直到 Promise 解决(即,已完成或已拒绝)。当执行恢复时,await 表达式的值将成为已完成 Promise 的值。
如果 Promise 被拒绝,await 表达式会抛出被拒绝的值。包含 await 表达式的函数将出现在错误的堆栈跟踪中。否则,如果被拒绝的 Promise 未被 await 或立即返回,则调用函数不会出现在堆栈跟踪中。
expression 的解析方式与 Promise.resolve() 相同:它总是被转换为原生 Promise,然后被等待。如果 expression 是一个
- 原生
Promise(这意味着expression属于Promise或其子类,且expression.constructor === Promise):Promise 直接被使用并原生等待,而无需调用then()。 - thenable 对象(包括非原生 Promise、polyfill、代理、子类等):通过调用对象的
then()方法并传入一个调用resolve回调的处理器,使用原生Promise()构造函数构造一个新的 Promise。 - 非 thenable 值:构造并使用一个已经完成的
Promise。
即使使用的 Promise 已经完成,async 函数的执行仍然会暂停到下一个 tick。同时,async 函数的调用者会恢复执行。请参阅下面的示例。
因为 await 只在 async 函数和模块内部有效,而它们本身是异步的并返回 Promise,所以 await 表达式从不阻塞主线程,并且只延迟实际依赖于结果的代码执行,即 await 表达式之后的所有内容。
示例
等待 Promise 完成
如果将 Promise 传递给 await 表达式,它会等待 Promise 完成并返回完成值。
function resolveAfter2Seconds(x) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
const x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
Thenable 对象
Thenable 对象的解析方式与实际的 Promise 对象相同。
async function f2() {
const thenable = {
then(resolve) {
resolve("resolved!");
},
};
console.log(await thenable); // "resolved!"
}
f2();
它们也可以被拒绝
async function f2() {
const thenable = {
then(_, reject) {
reject(new Error("rejected!"));
},
};
await thenable; // Throws Error: rejected!
}
f2();
转换为 Promise
如果值不是 Promise,await 会将该值转换为一个已解决的 Promise,并等待它。只要它没有可调用的 then 属性,被等待的值的标识就不会改变。
async function f3() {
const y = await 20;
console.log(y); // 20
const obj = {};
console.log((await obj) === obj); // true
}
f3();
处理被拒绝的 Promise
如果 Promise 被拒绝,则抛出被拒绝的值。
async function f4() {
try {
const z = await Promise.reject(new Error("rejected!"));
} catch (e) {
console.error(e); // Error: rejected!
}
}
f4();
您可以通过在等待 Promise 之前链接一个 catch() 处理器来处理被拒绝的 Promise,而无需 try 块。
const response = await promisedFunction().catch((err) => {
console.error(err);
return "default response";
});
// response will be "default response" if the promise is rejected
这建立在 promisedFunction() 从不同步抛出错误,而是总是返回一个被拒绝的 Promise 的假设上。对于大多数设计良好的基于 Promise 的函数来说都是如此,它们通常看起来像
function promisedFunction() {
// Immediately return a promise to minimize chance of an error being thrown
return new Promise((resolve, reject) => {
// do something async
});
}
但是,如果 promisedFunction() 同步抛出错误,catch() 处理器将不会捕获该错误。在这种情况下,try...catch 语句是必要的。
顶层 await
您可以在 模块的顶层独立使用 await 关键字(在 async 函数之外)。这意味着使用 await 的子模块的模块将在子模块执行后才运行,同时不会阻塞其他子模块的加载。
这是一个使用 Fetch API 并在 export 语句中指定 await 的模块示例。任何包含此模块的模块都将等待 fetch 解决后才运行任何代码。
// fetch request
const colors = fetch("../data/colors.json").then((response) => response.json());
export default await colors;
await 的控制流影响
当代码中遇到 await(在 async 函数或模块中)时,被等待的表达式会被执行,而所有依赖于表达式值的代码都会暂停。控制权会离开函数并返回给调用者。当被等待表达式的值被解析时,会调度另一个继续暂停代码的 微任务。即使被等待的值是一个已经解析的 Promise 或不是 Promise,也会发生这种情况:执行不会返回到当前函数,直到所有其他已调度的微任务都已处理。例如,考虑以下代码
async function foo(name) {
console.log(name, "start");
console.log(name, "middle");
console.log(name, "end");
}
foo("First");
foo("Second");
// First start
// First middle
// First end
// Second start
// Second middle
// Second end
在这种情况下,函数 foo 在效果上是同步的,因为它不包含任何 await 表达式。这三个语句发生在同一个 tick 中。因此,这两个函数调用按顺序执行所有语句。在 Promise 术语中,该函数对应于
function foo(name) {
return new Promise((resolve) => {
console.log(name, "start");
console.log(name, "middle");
console.log(name, "end");
resolve();
});
}
但是,一旦有一个 await,函数就会变成异步的,并且后续语句的执行会延迟到下一个 tick。
async function foo(name) {
console.log(name, "start");
await console.log(name, "middle");
console.log(name, "end");
}
foo("First");
foo("Second");
// First start
// First middle
// Second start
// Second middle
// First end
// Second end
这对应于
function foo(name) {
return new Promise((resolve) => {
console.log(name, "start");
resolve(console.log(name, "middle"));
}).then(() => {
console.log(name, "end");
});
}
额外的 then() 处理器可以与传递给构造函数的执行器合并,因为它不等待任何异步操作。但是,它的存在会将代码分割成每个对 foo 的调用都有一个额外的微任务。这些微任务以交错的方式调度和执行,这既会使您的代码变慢,又会引入不必要的竞态条件。因此,请确保仅在必要时使用 await(将 Promise 解包为它们的值)。
微任务不仅由 Promise 解析调度,也由其他 Web API 调度,并且它们以相同的优先级执行。此示例使用 queueMicrotask() 来演示在遇到每个 await 表达式时微任务队列的处理方式。
let i = 0;
queueMicrotask(function test() {
i++;
console.log("microtask", i);
if (i < 3) {
queueMicrotask(test);
}
});
(async () => {
console.log("async function start");
for (let i = 1; i < 3; i++) {
await null;
console.log("async function resume", i);
}
await null;
console.log("async function end");
})();
queueMicrotask(() => {
console.log("queueMicrotask() after calling async function");
});
console.log("script sync part end");
// Logs:
// async function start
// script sync part end
// microtask 1
// async function resume 1
// queueMicrotask() after calling async function
// microtask 2
// async function resume 2
// microtask 3
// async function end
在此示例中,test() 函数总是在 async 函数恢复之前被调用,因此它们各自调度的微任务总是以交错的方式执行。另一方面,由于 await 和 queueMicrotask() 都调度微任务,执行顺序总是基于调度顺序。这就是为什么“queueMicrotask() after calling async function”日志在 async 函数第一次恢复之后发生的原因。
改进堆栈跟踪
有时,当 Promise 直接从 async 函数返回时,会省略 await。
async function noAwait() {
// Some actions...
return /* await */ lastAsyncTask();
}
然而,考虑 lastAsyncTask 异步抛出错误的情况。
async function lastAsyncTask() {
await null;
throw new Error("failed");
}
async function noAwait() {
return lastAsyncTask();
}
noAwait();
// Error: failed
// at lastAsyncTask
只有 lastAsyncTask 出现在堆栈跟踪中,因为 Promise 在它已经从 noAwait 返回后被拒绝——从某种意义上说,Promise 与 noAwait 无关。为了改进堆栈跟踪,您可以使用 await 解包 Promise,以便异常被抛入当前函数。然后,异常将立即被包装成一个新的被拒绝的 Promise,但在错误创建期间,调用者将出现在堆栈跟踪中。
async function lastAsyncTask() {
await null;
throw new Error("failed");
}
async function withAwait() {
return await lastAsyncTask();
}
withAwait();
// Error: failed
// at lastAsyncTask
// at async withAwait
与一些普遍的看法相反,由于规范和引擎优化原生 Promise 的解析方式,return await promise 至少与 return promise 一样快。有一个提案旨在使 return promise 更快,您还可以阅读有关V8 对 async 函数的优化。因此,除了风格原因外,return await 几乎总是首选。
规范
| 规范 |
|---|
| ECMAScript® 2026 语言规范 # sec-async-function-definitions |
浏览器兼容性
加载中…