await

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since April 2017.

await 运算符用于等待 Promise 并获取其履行值。它只能在 异步函数 内部或 module 的顶层使用。

¥The await operator is used to wait for a Promise and get its fulfillment value. It can only be used inside an async function or at the top level of a module.

语法

¥Syntax

js
await expression

参数

¥Parameters

expression

Promise可用对象 或任何要等待的值。

返回值

¥Return value

Promise 或 thenable 对象的实现值,或者,如果表达式不是 thenable,则为表达式自身的值。

¥The fulfillment value of the promise or thenable object, or, if the expression is not thenable, the expression's own value.

例外情况

¥Exceptions

如果 Promise 或 thenable 对象被拒绝,则抛出拒绝原因。

¥Throws the rejection reason if the promise or thenable object is rejected.

描述

¥Description

await 通常用于通过将 Promise 作为 expression 传递来解开 Promise。使用 await 会暂停其周围 async 函数的执行,直到承诺得到解决(即履行或拒绝)。当执行恢复时,await 表达式的值变为已履行的 Promise 的值。

¥await is usually used to unwrap promises by passing a Promise as the expression. Using await pauses the execution of its surrounding async function until the promise is settled (that is, fulfilled or rejected). When execution resumes, the value of the await expression becomes that of the fulfilled promise.

如果 Promise 被拒绝,则 await 表达式会抛出被拒绝的值。包含 await 表达式的函数将出现 出现在堆栈跟踪中 错误。否则,如果未等待或立即返回被拒绝的 Promise,则调用者函数将不会出现在堆栈跟踪中。

¥If the promise is rejected, the await expression throws the rejected value. The function containing the await expression will appear in the stack trace of the error. Otherwise, if the rejected promise is not awaited or is immediately returned, the caller function will not appear in the stack trace.

expression 的解析方式与 Promise.resolve() 相同:它总是被转换为本地 Promise,然后等待。如果 expression 是:

¥The expression is resolved in the same way as Promise.resolve(): it's always converted to a native Promise and then awaited. If the expression is a:

  • 原生 Promise(这意味着 expression 属于 Promise 或其子类,以及 expression.constructor === Promise):Promise 直接使用并原生等待,无需调用 then()
  • 则可对象(包括非原生 Promise、polyfill、代理、子类等):通过调用对象的 then() 方法并传入调用 resolve 回调的处理程序,使用原生 Promise() 构造函数构造新的 Promise。
  • 不可实现的值:构建并使用了已实现的 Promise

即使使用的 Promise 已经履行,异步函数的执行仍然会暂停,直到下一个时钟周期。与此同时,异步函数的调用者恢复执行。请参阅下面的示例。

¥Even when the used promise is already fulfilled, the async function's execution still pauses until the next tick. In the meantime, the caller of the async function resumes execution. See example below.

因为 await 仅在异步函数和模块内部有效,而它们本身是异步的并返回承诺,所以 await 表达式永远不会阻塞主线程,而只会推迟实际依赖于结果的代码的执行,即 await 表达式之后的任何代码。

¥Because await is only valid inside async functions and modules, which themselves are asynchronous and return promises, the await expression never blocks the main thread and only defers execution of code that actually depends on the result, i.e. anything after the await expression.

示例

¥Examples

等待兑现承诺

¥Awaiting a promise to be fulfilled

如果将 Promise 传递给 await 表达式,则它会等待 Promise 满足并返回满足的值。

¥If a Promise is passed to an await expression, it waits for the Promise to be fulfilled and returns the fulfilled value.

js
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 objects

可处理的对象 的解析方式与实际的 Promise 对象相同。

¥Thenable objects are resolved just the same as actual Promise objects.

js
async function f() {
  const thenable = {
    then(resolve, _reject) {
      resolve("resolved!");
    },
  };
  console.log(await thenable); // "resolved!"
}

f();

他们也可以被拒绝:

¥They can also be rejected:

js
async function f() {
  const thenable = {
    then(resolve, reject) {
      reject(new Error("rejected!"));
    },
  };
  await thenable; // Throws Error: rejected!
}

f();

转换为承诺

¥Conversion to promise

如果该值不是 Promise,则 await 将该值转换为已解析的 Promise,并等待它。只要等待值不具有可调用的 then 属性,它的标识就不会改变。

¥If the value is not a Promise, await converts the value to a resolved Promise, and waits for it. The awaited value's identity doesn't change as long as it doesn't have a then property that's callable.

js
async function f3() {
  const y = await 20;
  console.log(y); // 20

  const obj = {};
  console.log((await obj) === obj); // true
}

f3();

处理被拒绝的承诺

¥Handling rejected promises

如果 Promise 被拒绝,则抛出拒绝的值。

¥If the Promise is rejected, the rejected value is thrown.

js
async function f4() {
  try {
    const z = await Promise.reject(30);
  } catch (e) {
    console.error(e); // 30
  }
}

f4();

你可以在等待承诺之前链接 catch() 处理程序,在没有 try 块的情况下处理被拒绝的承诺。

¥You can handle rejected promises without a try block by chaining a catch() handler before awaiting the promise.

js
const response = await promisedFunction().catch((err) => {
  console.error(err);
  return "default response";
});
// response will be "default response" if the promise is rejected

这是建立在 promisedFunction() 从不同步抛出错误,但总是返回被拒绝的承诺的假设之上的。大多数正确设计的基于 Promise 的函数都是这种情况,通常如下所示:

¥This is built on the assumption that promisedFunction() never synchronously throws an error, but always returns a rejected promise. This is the case for most properly-designed promise-based functions, which usually look like:

js
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 语句是必要的。

¥However, if promisedFunction() does throw an error synchronously, the error won't be caught by the catch() handler. In this case, the try...catch statement is necessary.

顶层等待

¥Top level await

你可以在 module 的顶层单独使用 await 关键字(在异步函数之外)。这意味着具有使用 await 的子模块的模块将等待子模块执行后再运行,同时不会阻止其他子模块加载。

¥You can use the await keyword on its own (outside of an async function) at the top level of a module. This means that modules with child modules that use await will wait for the child modules to execute before they themselves run, all while not blocking other child modules from loading.

以下是使用 获取 API 并在 export 语句中指定 wait 的简单模块示例。包含此内容的任何模块都将在运行任何代码之前等待提取解析。

¥Here is an example of a simple module using the Fetch API and specifying await within the export statement. Any modules that include this will wait for the fetch to resolve before running any code.

js
// fetch request
const colors = fetch("../data/colors.json").then((response) => response.json());

export default await colors;

控制 await 的流程效果

¥Control flow effects of await

当代码中(无论是在异步函数中还是在模块中)遇到 await 时,将执行等待的表达式,同时所有依赖于表达式值的代码都会暂停并推送到 微任务队列 中。然后主线程被释放以执行事件循环中的下一个任务。即使等待的值是已解决的承诺或不是承诺,也会发生这种情况。例如,考虑以下代码:

¥When an await is encountered in code (either in an async function or in a module), the awaited expression is executed, while all code that depends on the expression's value is paused and pushed into the microtask queue. The main thread is then freed for the next task in the event loop. This happens even if the awaited value is an already-resolved promise or not a promise. For example, consider the following code:

js
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 表达式。这三个语句发生在同一时刻。用承诺术语来说,该函数对应于:

¥In this case, the two async functions are synchronous in effect, because they don't contain any await expression. The three statements happen in the same tick. In promise terms, the function corresponds to:

js
function foo(name) {
  return new Promise((resolve) => {
    console.log(name, "start");
    console.log(name, "middle");
    console.log(name, "end");
    resolve();
  });
}

但是,一旦有一个 await,该函数就会变为异步,并且以下语句的执行将推迟到下一个时钟周期。

¥However, as soon as there's one await, the function becomes asynchronous, and execution of following statements is deferred to the next tick.

js
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

这对应于:

¥This corresponds to:

js
function foo(name) {
  return new Promise((resolve) => {
    console.log(name, "start");
    resolve(console.log(name, "middle"));
  }).then(() => {
    console.log(name, "end");
  });
}

虽然额外的 then() 处理程序不是必需的,并且该处理程序可以与传递给构造函数的执行程序合并,但 then() 处理程序的存在意味着代码将需要额外的一个时钟周期才能完成。await 也会发生同样的情况。因此,请确保仅在必要时使用 await(将 Promise 解包到其值中)。

¥While the extra then() handler is not necessary, and the handler can be merged with the executor passed to the constructor, the then() handler's existence means the code will take one extra tick to complete. The same happens for await. Therefore, make sure to use await only when necessary (to unwrap promises into their values).

其他微任务可以在异步函数恢复之前执行。本示例使用 queueMicrotask() 来演示当遇到每个 await 表达式时微任务队列是如何处理的。

¥Other microtasks can execute before the async function resumes. This example uses queueMicrotask() to demonstrate how the microtask queue is processed when each await expression is encountered.

js
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() 函数始终在异步函数恢复之前调用,因此它们各自调度的微任务始终以交织的方式执行。另一方面,由于 awaitqueueMicrotask() 都调度微任务,所以执行的顺序始终以调度的顺序为准。这就是为什么异步函数第一次恢复后会出现 "调用异步函数后的 queueMicrotask()" 日志的原因。

¥In this example, the test() function is always called before the async function resumes, so the microtasks they each schedule are always executed in an intertwined fashion. On the other hand, because both await and queueMicrotask() schedule microtasks, the order of execution is always based on the order of scheduling. This is why the "queueMicrotask() after calling async function" log happens after the async function resumes for the first time.

改进堆栈跟踪

¥Improving stack trace

有时,当从异步函数直接返回 Promise 时,await 会被省略。

¥Sometimes, the await is omitted when a promise is directly returned from an async function.

js
async function noAwait() {
  // Some actions...

  return /* await */ lastAsyncTask();
}

但是,请考虑 lastAsyncTask 异步抛出错误的情况。

¥However, consider the case where lastAsyncTask asynchronously throws an error.

js
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 中,但在错误创建期间,调用者将出现在堆栈跟踪中。

¥Only lastAsyncTask appears in the stack trace, because the promise is rejected after it has already been returned from noAwait — in some sense, the promise is unrelated to noAwait. To improve the stack trace, you can use await to unwrap the promise, so that the exception gets thrown into the current function. The exception will then be immediately wrapped into a new rejected promise, but during error creation, the caller will appear in the stack trace.

js
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 会带来一些性能损失,因为承诺必须被打开并再次封装。

¥However, there's a little performance penalty coming with return await because the promise has to be unwrapped and wrapped again.

规范

Specification
ECMAScript Language Specification
# sec-async-function-definitions

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看