异步函数

基线 广泛可用

此功能已经建立,并且可以在许多设备和浏览器版本上运行。它从以下时间起在浏览器中可用 2017 年 4 月.

**async function** 声明创建了一个新的异步函数到给定名称的 绑定await 关键字在函数体内允许,使异步、基于 promise 的行为能够以更清晰的风格编写,避免了显式配置 promise 链的需要。

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

试试看

语法

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。控制权移至返回表达式(如果有)。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 链是在控制权从异步函数中依次释放并返回到异步函数时逐步构建的。因此,在处理并发异步操作时,我们必须注意错误处理行为。

例如,在以下代码中,即使 .catch 处理程序已在 promise 链中配置得更远,也会抛出一个未处理的 promise 拒绝错误。这是因为 p2 只有在控制权从 p1 返回时才会“连接”到 promise 链中。

js
async 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]; // 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 完成。在这种情况下,最快的计时器结果会在最慢的计时器结果之后处理。

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

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

sequentialWait 中,如果 promise fast 在 promise slow 完成之前拒绝,则会引发未处理的 promise 拒绝错误,无论调用者是否配置了 catch 子句。

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

使用 async 函数重写 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
}

它可以用单个 async 函数重写,如下所示

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 语句,尽管这也有效:async 函数的返回值被隐式包装在 Promise.resolve 中 - 如果它本身还不是 promise(如示例所示)。

规格

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

浏览器兼容性

BCD 表格仅在浏览器中加载

另请参阅