TypeScript 中 await 的詳解
- 1. 基本概念
- 2. 語法要求
- 3. 工作原理
- 4. 與 Promise 的比較
- 5. 實踐中的注意事項
- 總結
本文詳細介紹了 TypeScript 中
await
的工作原理、語法要求、與 Promise 的關系以及實踐中需要注意的問題,同時針對代碼示例進行了優化和補充說明。
1. 基本概念
-
異步編程背景
傳統異步編程常采用回調函數或 Promise 鏈式調用,這容易導致“回調地獄”(即指的是在處理異步操作時,由于多個回調函數層層嵌套,導致代碼結構混亂、可讀性差、難以維護和調試的現象)。async/await
提供了類似同步代碼的寫法,使邏輯更清晰,便于調試和錯誤處理。 -
await
的作用await
用于等待一個 Promise 的解析結果。當執行到await
表達式時,當前的 async 函數暫停執行,直到等待的 Promise 進入 成功(resolve) 或 拒絕(reject) 狀態,然后繼續執行后續代碼。- 此外,async 函數會自動捕獲同步錯誤,將其轉換為返回 Promise 的拒絕狀態。
-
async 函數返回值及狀態變化
-
async 函數總是返回一個 Promise:
- 如果函數返回一個非 Promise 值,會等同于返回
Promise.resolve(值)
; - 如果函數內部拋出異常(無論同步或異步錯誤),返回的 Promise 則進入拒絕狀態(reject),異常作為拒絕原因。
- 如果函數返回一個非 Promise 值,會等同于返回
-
示例:
async function getNumber() {return 42; // 等同于 return Promise.resolve(42) }async function throwError() {throw new Error("失敗"); // 返回被拒絕的 Promise,狀態為 reject }
-
2. 語法要求
-
使用限制
await
只能在標記為async
的函數內部使用,否則會導致語法錯誤。例如:async function getData() {const response = await fetch("https://api.example.com/data");return response.json(); }
-
對非 Promise 值的處理
如果await
后面的表達式不是一個 Promise,執行時會直接返回該值(等同于await Promise.resolve(值)
)。例如:async function testNonPromise() {const result = await 42; // 直接返回 42,等同于 await Promise.resolve(42)console.log(result); // 輸出 42 } testNonPromise();
-
對 thenable 對象的處理
如果表達式是一個具有then
方法的對象,則會按照 Promise 的規則處理。
3. 工作原理
-
暫停與狀態機
當遇到await
表達式時,當前 async 函數會暫停執行,其內部狀態被保存。等待 Promise 解析后,會恢復執行。在編譯為 ES5 等低版本目標時,TypeScript 會生成類似生成器函數的狀態機代碼,通常借助__awaiter
輔助函數實現。 -
非阻塞主線程
盡管 async 函數內部暫停執行,但這不會阻塞 JavaScript 的事件循環,主線程仍可響應其他任務。 -
錯誤處理
錯誤處理方式有兩種:-
try/catch 捕獲:
可集中處理多個 await 操作中的錯誤,適用于同步與異步錯誤均可捕獲。async function fetchData() {try {const response = await fetch("https://api.example.com/data");if (!response.ok) throw new Error("請求失敗");const data = await response.json();console.log(data);} catch (error) {console.error("獲取數據失敗:", error);} }
說明:try/catch 塊不僅能捕獲 await 等待期間的異步錯誤,還能捕獲函數內部拋出的同步錯誤。
-
使用
.catch()
方法:
可在單個 Promise 后直接捕獲錯誤,并返回默認值以便后續流程繼續。async function fetchDataWithCatch() {const response = await fetch("https://api.example.com/data").catch(error => {console.error("獲取數據失敗:", error);return null;});if (response) {if (!response.ok) throw new Error("請求失敗");const data = await response.json();console.log(data);} }
-
4. 與 Promise 的比較
-
可讀性提升
使用async/await
使得代碼邏輯看起來更接近同步流程,避免了大量.then()
的嵌套,使錯誤處理更為集中。 -
編譯轉換細節
TypeScript 編譯器會將async/await
轉換為基于 Promise 的實現。在目標環境為 ES5 或 ES6 時,轉換后的代碼可能會借助__awaiter
輔助函數或生成器函數實現狀態機邏輯。例如:async function fetchData() {const result = await fetch("https://api.example.com/data");if (!result.ok) throw new Error("請求失敗");return await result.json(); }
轉換后相當于:
function fetchData() {return __awaiter(this, void 0, void 0, function* () {const result = yield fetch("https://api.example.com/data");if (!result.ok) throw new Error("請求失敗");return yield result.json();}); }
說明:這里展示的轉換邏輯只是示例,具體實現依賴 TypeScript 版本和目標運行環境。
5. 實踐中的注意事項
-
錯誤處理策略
對每個 await 操作都建議采用 try/catch 或在調用處使用.catch()
來捕獲錯誤,確保程序健壯性。
例如:async function riskyOperation() {const response = await fetch("https://api.example.com/data");if (!response.ok) throw new Error("請求失敗");return response.json(); }riskyOperation().catch(error => {console.error("外部捕獲錯誤:", error); });
-
并行與串行操作
對于多個互不依賴的異步操作,若依次使用 await 會導致串行執行,從而影響性能。建議使用Promise.all
并行處理:async function fetchMultipleData() {const [data1, data2] = await Promise.all([fetch("https://api.example.com/data1").then(res => {if (!res.ok) throw new Error("data1 請求失敗");return res.json();}),fetch("https://api.example.com/data2").then(res => {if (!res.ok) throw new Error("data2 請求失敗");return res.json();})]);console.log(data1, data2); }
若希望即使部分操作失敗也能獲得全部結果,則可使用
Promise.allSettled
:async function fetchMultipleDataWithAllSettled() {const results = await Promise.allSettled([fetch("https://api.example.com/data1").then(res => res.json()),fetch("https://api.example.com/data2").then(res => res.json())]);results.forEach(result => {if (result.status === 'fulfilled') {console.log(result.value);} else {console.error("失敗原因:", result.reason);}}); }
說明:在
Promise.allSettled
的示例中,result.reason 類型為 any,視情況可進行類型斷言處理。 -
其他實踐建議
- HTTP 狀態碼檢查:建議對 fetch 返回的響應進行
response.ok
檢查,以確保請求成功 - 循環中的 await:當需要對異步迭代(如讀取流、異步生成器)時,可使用
for-await-of
循環 - top-level await:在模塊環境下(ESM 模塊)TypeScript 也支持頂層 await,但需要確保目標環境的兼容性
- 術語統一:文中統一使用“拒絕(reject)”描述 Promise 的拒絕狀態
- HTTP 狀態碼檢查:建議對 fetch 返回的響應進行
總結
通過使用 async/await
,我們可以編寫出邏輯清晰、易于調試和維護的異步代碼。關鍵要點包括:
await
只能在 async 函數內使用;- async 函數返回 Promise,內部的同步錯誤會轉換為 Promise 的拒絕狀態;
- 合理使用 try/catch 和
.catch()
進行錯誤處理; - 對于多個互不依賴的異步操作,建議采用并行執行方式(如
Promise.all
或Promise.allSettled
)以提升性能; - 需注意 HTTP 響應狀態碼、循環中的異步處理以及 top-level await 的環境要求。
同時需要認識到,async/await
并非完全替代 Promise,而是對其進行封裝和補充,使得異步代碼在語義和結構上更加直觀。