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

瀏覽器相容性

另見