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,而是对其进行封装和补充,使得异步代码在语义和结构上更加直观。