async function

Baseline 已广泛支持

该特性已非常成熟,可在多种设备和浏览器版本上使用。自 2017 年 4 月以来,它已在各大浏览器上可用。

async function 声明为给定的名称创建了一个新的异步函数的绑定。在函数体中允许使用 await 关键字,它使得异步的、基于 Promise 的行为可以用更简洁的方式编写,避免了显式配置 Promise 链的需要。

你也可以使用 async function 表达式定义异步函数。

试一试

function resolveAfter2Seconds() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("resolved");
    }, 2000);
  });
}

async function asyncCall() {
  console.log("calling");
  const result = await resolveAfter2Seconds();
  console.log(result);
  // Expected output: "resolved"
}

asyncCall();

语法

js
async function name(param0) {
  statements
}
async function name(param0, param1) {
  statements
}
async function name(param0, param1, /* …, */ paramN) {
  statements
}

注意: asyncfunction 之间不能有行终止符,否则会自动插入分号,导致 async 成为标识符,其余部分成为 function 声明。

参数

name

函数的名称。

param 可选

函数形式参数的名称。有关参数的语法,请参阅函数参考

statements 可选

构成函数体的语句。可以使用 await 机制。

描述

async function 声明创建了一个 AsyncFunction 对象。每次调用异步函数时,它都会返回一个新的 Promise,该 Promise 将通过异步函数返回的值来解析,或者通过异步函数中未捕获的异常来拒绝。

异步函数可以包含零个或多个 await 表达式。Await 表达式使返回 Promise 的函数表现得像同步函数一样,通过暂停执行直到返回的 Promise 被实现或拒绝。Promise 的解析值被视为 await 表达式的返回值。使用 asyncawait 可以在异步代码周围使用普通的 try / catch 块。

注意: await 关键字仅在常规 JavaScript 代码中的异步函数内部有效。如果你在异步函数体外部使用它,你将得到一个 SyntaxError

await 可以单独用于JavaScript 模块

注意: async/await 的目的是简化使用基于 Promise 的 API 所需的语法。async/await 的行为类似于结合生成器和 Promise。

异步函数总是返回一个 Promise。如果异步函数的返回值不是显式 Promise,它将隐式地被封装在一个 Promise 中。

例如,考虑以下代码

js
async function foo() {
  return 1;
}

它类似于

js
function foo() {
  return Promise.resolve(1);
}

请注意,尽管异步函数的返回值表现得好像被 Promise.resolve 封装了一样,但它们并不等价。异步函数将返回不同的引用,而 Promise.resolve 如果给定值是一个 Promise,则返回相同的引用。当你想要检查 Promise 和异步函数返回值的相等性时,这可能是一个问题。

js
const 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 表达式,异步函数将始终异步完成。

例如

js
async function foo() {
  await 1;
}

它也等价于

js
function foo() {
  return Promise.resolve(1).then(() => undefined);
}

每个 await 表达式之后的代码可以被认为是存在于一个 .then 回调中。这样,随着函数每次重入步骤,Promise 链会逐步构建。返回值构成了链中的最终环节。

在下面的例子中,我们依次等待两个 Promise。执行通过函数 foo 分为三个阶段。

  1. 函数 foo 主体的第一行同步执行,await 表达式配置了待定的 Promise。然后 foo 的执行暂停,控制权返回给调用 foo 的函数。
  2. 一段时间后,当第一个 Promise 被实现或拒绝时,控制权返回到 foo。第一个 Promise 实现的结果(如果未被拒绝)从 await 表达式返回。这里 1 被赋值给 result1。执行继续,第二个 await 表达式被求值。再次,foo 的执行暂停,控制权被让出。
  3. 一段时间后,当第二个 Promise 被实现或拒绝时,控制权重新进入 foo。第二个 Promise 解析的结果从第二个 await 表达式返回。这里 2 被赋值给 result2。控制权移动到 return 表达式(如果存在)。undefined 的默认返回值作为当前 Promise 的解析值返回。
js
async 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 将不会“连接”到 Promise 链中,直到控制权从 p1 返回。

js
async function foo() {
  const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000));
  const p2 = new Promise((_, reject) =>
    setTimeout(() => reject(new Error("failed")), 500),
  );
  const results = [await p1, await p2]; // Do not do this! Use Promise.all or Promise.allSettled instead.
}
foo().catch(() => {}); // Attempt to swallow all errors...

async function 声明的行为类似于function 声明——它们被提升到其作用域的顶部,可以在其作用域内的任何地方调用,并且只能在特定上下文中重新声明。

示例

异步函数和执行顺序

js
function resolveAfter2Seconds() {
  console.log("starting slow promise");
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("slow");
      console.log("slow promise is done");
    }, 2000);
  });
}

function resolveAfter1Second() {
  console.log("starting fast promise");
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("fast");
      console.log("fast promise is done");
    }, 1000);
  });
}

async function sequentialStart() {
  console.log("== sequentialStart starts ==");

  // 1. Start a timer, log after it's done
  const slow = resolveAfter2Seconds();
  console.log(await slow);

  // 2. Start the next timer after waiting for the previous one
  const fast = resolveAfter1Second();
  console.log(await fast);

  console.log("== sequentialStart done ==");
}

async function sequentialWait() {
  console.log("== sequentialWait starts ==");

  // 1. Start two timers without waiting for each other
  const slow = resolveAfter2Seconds();
  const fast = resolveAfter1Second();

  // 2. Wait for the slow timer to complete, and then log the result
  console.log(await slow);
  // 3. Wait for the fast timer to complete, and then log the result
  console.log(await fast);

  console.log("== sequentialWait done ==");
}

async function concurrent1() {
  console.log("== concurrent1 starts ==");

  // 1. Start two timers concurrently and wait for both to complete
  const results = await Promise.all([
    resolveAfter2Seconds(),
    resolveAfter1Second(),
  ]);
  // 2. Log the results together
  console.log(results[0]);
  console.log(results[1]);

  console.log("== concurrent1 done ==");
}

async function concurrent2() {
  console.log("== concurrent2 starts ==");

  // 1. Start two timers concurrently, log immediately after each one is done
  await Promise.all([
    (async () => console.log(await resolveAfter2Seconds()))(),
    (async () => console.log(await resolveAfter1Second()))(),
  ]);
  console.log("== concurrent2 done ==");
}

sequentialStart(); // after 2 seconds, logs "slow", then after 1 more second, "fast"

// wait above to finish
setTimeout(sequentialWait, 4000); // after 2 seconds, logs "slow" and then "fast"

// wait again
setTimeout(concurrent1, 7000); // same as sequentialWait

// wait again
setTimeout(concurrent2, 10000); // after 1 second, logs "fast", then after 1 more second, "slow"

await 和并发

sequentialStart 中,第一个 await 会暂停执行 2 秒,然后第二个 await 会再暂停 1 秒。第二个计时器在前一个计时器触发之前不会创建,因此代码在 3 秒后完成。

sequentialWait 中,两个计时器都被创建,然后被 await。计时器并发运行,这意味着代码在 2 秒而不是 3 秒内完成,即最慢的计时器。然而,await 调用仍然是串行运行的,这意味着第二个 await 将等待第一个 await 完成。在这种情况下,最快计时器的结果在最慢计时器之后处理。

如果你希望在两个或更多并发运行并完成的作业之后安全地执行其他作业,你必须在该作业之前 await 调用 Promise.all()Promise.allSettled()

警告: 函数 sequentialWaitconcurrent1 在功能上并不等价。

sequentialWait 中,如果 Promise fast 在 Promise slow 被实现之前被拒绝,则无论调用者是否配置了 catch 子句,都会抛出未处理的 Promise 拒绝错误。

concurrent1 中,Promise.all 一次性连接了 Promise 链,这意味着无论 Promise 拒绝的顺序如何,操作都会快速失败,并且错误将始终发生在配置的 Promise 链中,从而能够以正常方式捕获。

用异步函数重写 Promise 链

返回 Promise 的 API 会导致 Promise 链,它将函数分成许多部分。考虑以下代码

js
function getProcessedData(url) {
  return downloadData(url) // returns a promise
    .catch((e) => downloadFallbackData(url)) // returns a promise
    .then((v) => processDataInWorker(v)); // returns a promise
}

可以用一个异步函数重写如下

js
async function getProcessedData(url) {
  let v;
  try {
    v = await downloadData(url);
  } catch (e) {
    v = await downloadFallbackData(url);
  }
  return processDataInWorker(v);
}

或者,你可以用 catch() 链接 Promise

js
async function getProcessedData(url) {
  const v = await downloadData(url).catch((e) => downloadFallbackData(url));
  return processDataInWorker(v);
}

在重写的两个版本中,请注意 return 关键字后没有 await 语句,尽管那也是有效的:异步函数的返回值会隐式地封装在 Promise.resolve 中——如果它本身还不是一个 Promise(如示例中所示)。

规范

规范
ECMAScript® 2026 语言规范
# sec-async-function-definitions

浏览器兼容性

另见