await
Baseline 廣泛可用 *
語法
await expression
引數
表示式-
一個
Promise、一個 thenable 物件,或任何需要等待的值。
返回值
Promise 或 thenable 物件的完成值,或者,如果表示式不是 thenable,則為表示式本身的值。
異常
如果 Promise 或 thenable 物件被拒絕,則丟擲拒絕原因。
描述
await 通常透過將 Promise 作為 expression 傳遞來解包 Promise。使用 await 會暫停其所在 async 函式的執行,直到 Promise 解決(即,已完成或已拒絕)。當執行恢復時,await 表示式的值將成為已完成 Promise 的值。
如果 Promise 被拒絕,await 表示式會丟擲被拒絕的值。包含 await 表示式的函式將出現在錯誤的堆疊跟蹤中。否則,如果被拒絕的 Promise 未被 await 或立即返回,則呼叫函式不會出現在堆疊跟蹤中。
expression 的解析方式與 Promise.resolve() 相同:它總是被轉換為原生 Promise,然後被等待。如果 expression 是一個
- 原生
Promise(這意味著expression屬於Promise或其子類,且expression.constructor === Promise):Promise 直接被使用並原生等待,而無需呼叫then()。 - thenable 物件(包括非原生 Promise、polyfill、代理、子類等):透過呼叫物件的
then()方法並傳入一個呼叫resolve回撥的處理器,使用原生Promise()建構函式構造一個新的 Promise。 - 非 thenable 值:構造並使用一個已經完成的
Promise。
即使使用的 Promise 已經完成,async 函式的執行仍然會暫停到下一個 tick。同時,async 函式的呼叫者會恢復執行。請參閱下面的示例。
因為 await 只在 async 函式和模組內部有效,而它們本身是非同步的並返回 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 f2() {
const thenable = {
then(resolve) {
resolve("resolved!");
},
};
console.log(await thenable); // "resolved!"
}
f2();
它們也可以被拒絕
async function f2() {
const thenable = {
then(_, reject) {
reject(new Error("rejected!"));
},
};
await thenable; // Throws Error: rejected!
}
f2();
轉換為 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(new Error("rejected!"));
} catch (e) {
console.error(e); // Error: rejected!
}
}
f4();
您可以透過在等待 Promise 之前連結一個 catch() 處理器來處理被拒絕的 Promise,而無需 try 塊。
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 關鍵字(在 async 函式之外)。這意味著使用 await 的子模組的模組將在子模組執行後才執行,同時不會阻塞其他子模組的載入。
這是一個使用 Fetch API 並在 export 語句中指定 await 的模組示例。任何包含此模組的模組都將等待 fetch 解決後才執行任何程式碼。
// fetch request
const colors = fetch("../data/colors.json").then((response) => response.json());
export default await colors;
await 的控制流影響
當代碼中遇到 await(在 async 函式或模組中)時,被等待的表示式會被執行,而所有依賴於表示式值的程式碼都會暫停。控制權會離開函式並返回給呼叫者。當被等待表示式的值被解析時,會排程另一個繼續暫停程式碼的 微任務。即使被等待的值是一個已經解析的 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
在這種情況下,函式 foo 在效果上是同步的,因為它不包含任何 await 表示式。這三個語句發生在同一個 tick 中。因此,這兩個函式呼叫按順序執行所有語句。在 Promise 術語中,該函式對應於
function foo(name) {
return new Promise((resolve) => {
console.log(name, "start");
console.log(name, "middle");
console.log(name, "end");
resolve();
});
}
但是,一旦有一個 await,函式就會變成非同步的,並且後續語句的執行會延遲到下一個 tick。
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() 處理器可以與傳遞給建構函式的執行器合併,因為它不等待任何非同步操作。但是,它的存在會將程式碼分割成每個對 foo 的呼叫都有一個額外的微任務。這些微任務以交錯的方式排程和執行,這既會使您的程式碼變慢,又會引入不必要的競態條件。因此,請確保僅在必要時使用 await(將 Promise 解包為它們的值)。
微任務不僅由 Promise 解析排程,也由其他 Web API 排程,並且它們以相同的優先順序執行。此示例使用 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() 函式總是在 async 函式恢復之前被呼叫,因此它們各自排程的微任務總是以交錯的方式執行。另一方面,由於 await 和 queueMicrotask() 都排程微任務,執行順序總是基於排程順序。這就是為什麼“queueMicrotask() after calling async function”日誌在 async 函式第一次恢復之後發生的原因。
改進堆疊跟蹤
有時,當 Promise 直接從 async 函式返回時,會省略 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
與一些普遍的看法相反,由於規範和引擎最佳化原生 Promise 的解析方式,return await promise 至少與 return promise 一樣快。有一個提案旨在使 return promise 更快,您還可以閱讀有關V8 對 async 函式的最佳化。因此,除了風格原因外,return await 幾乎總是首選。
規範
| 規範 |
|---|
| ECMAScript® 2026 語言規範 # sec-async-function-definitions |
瀏覽器相容性
載入中…