await
语法
await expression
参数
表达式
-
一个
Promise
,一个 thenable 对象,或任何要等待的值。
返回值
promise 或 thenable 对象的完成值,或者,如果表达式不是 thenable,则表达式的自身值。
异常
如果 promise 或 thenable 对象被拒绝,则抛出拒绝原因。
描述
await
通常用于通过将 Promise
作为 表达式
传递来解包 promise。使用 await
会暂停其周围 async
函数的执行,直到 promise 解决(即完成或拒绝)。当执行恢复时,await
表达式的值将变为完成的 promise 的值。
如果 promise 被拒绝,await
表达式将抛出拒绝值。包含 await
表达式的函数将 出现在错误的堆栈跟踪中。否则,如果拒绝的 promise 没有被等待或立即返回,则调用者函数将不会出现在堆栈跟踪中。
表达式
的解析方式与 Promise.resolve()
相同:它始终被转换为本机 Promise
,然后被等待。如果 表达式
是
- 本机
Promise
(这意味着表达式
属于Promise
或子类,并且表达式.constructor === Promise
):promise 将直接使用并本机等待,无需调用then()
。 - Thenable 对象(包括非本机 promise,polyfill,代理,子类等):使用本机
Promise()
构造函数通过调用对象的then()
方法并传递一个调用resolve
回调的处理程序来构造一个新 promise。 - 非 thenable 值:一个已经完成的
Promise
被构造并使用。
即使使用的 promise 已经完成,异步函数的执行仍然会暂停,直到下一个事件循环。在此期间,异步函数的调用者会恢复执行。 参见下面的示例。
由于 await
仅在异步函数和模块内部有效,而异步函数和模块本身是异步的并返回 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 f() {
const thenable = {
then(resolve, _reject) {
resolve("resolved!");
},
};
console.log(await thenable); // "resolved!"
}
f();
它们也可以被拒绝
async function f() {
const thenable = {
then(resolve, reject) {
reject(new Error("rejected!"));
},
};
await thenable; // Throws Error: rejected!
}
f();
转换为 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(30);
} catch (e) {
console.error(e); // 30
}
}
f4();
您可以在等待 promise 之前通过链接一个 catch()
处理程序来处理拒绝的 promise。
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 的控制流影响
当代码中遇到 await
(无论是在异步函数中还是在模块中),都会执行等待的表达式,而所有依赖表达式值的代码都会被暂停并推入 微任务队列。然后,主线程将释放用于事件循环中的下一个任务。即使等待的值是已经完成的 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
在这种情况下,两个异步函数实际上是同步的,因为它们不包含任何 await
表达式。三个语句在同一个事件循环中发生。在 promise 术语中,该函数对应于
function foo(name) {
return new Promise((resolve) => {
console.log(name, "start");
console.log(name, "middle");
console.log(name, "end");
resolve();
});
}
但是,只要有一个 await
,该函数就变为异步函数,后续语句的执行将推迟到下一个事件循环。
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()
处理程序不是必需的,并且可以将处理程序与传递给构造函数的执行器合并,但 then()
处理程序的存在意味着代码将多花费一个事件循环才能完成。await
也是如此。因此,请确保仅在必要时(将 promise 解包为其值)使用 await
。
其他微任务可以在异步函数恢复之前执行。此示例使用 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()
函数始终在异步函数恢复之前调用,因此它们各自安排的微任务始终以交织的方式执行。另一方面,由于 await
和 queueMicrotask()
都安排了微任务,因此执行顺序始终基于调度顺序。这就是为什么 "调用异步函数后的 queueMicrotask()" 日志在异步函数第一次恢复后发生的。
改进堆栈跟踪
有时,当 promise 直接从异步函数返回时,会省略 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
与一些普遍的看法相反,return await promise
至少与 return promise
一样快,这是因为规范和引擎对原生 Promise 的解析进行了优化。有一个提案要使 return promise
更快,你还可以阅读关于V8 对异步函数的优化。因此,除了风格原因外,return await
几乎总是更可取的。
规范
规范 |
---|
ECMAScript 语言规范 # sec-async-function-definitions |
浏览器兼容性
BCD 表格只在浏览器中加载
另请参阅
async function
async function
表达式AsyncFunction
- 顶级 await 在 v8.dev 上 (2019)
- typescript-eslint 规则:
return-await