在 TypeScript 中,async 和 await 是处理异步操作的语法糖 (Syntactic Sugar),本质上它们仍然是基于 Promise 的。
但在 TypeScript 的语境下,理解它们的核心在于掌握 返回类型推断 和 解包(Unwrapping) 机制。
以下是 async 和 await 的详细解析:
1. async 关键字
async 用于修饰函数。它有两个核心规则:
- 强制返回 Promise :无论函数内部
return什么,最终都会被封装成一个 Promise。 - 类型注解 :在 TypeScript 中,
async函数的返回值类型必须明确写成Promise<T>。
示例代码
typescript
// 1. 自动推断
async function simple() {
return 123;
}
// TS 自动推断出 simple() 的返回类型是: Promise<number>
// 2. 显式注解 (推荐)
// 即使你 return 的是一个普通字符串,类型定义必须包裹在 Promise 中
async function getUserName(): Promise<string> {
return "Alice";
}
// 3. 无返回值的异步函数
async function logData(): Promise<void> {
console.log("Saving...");
// 相当于 return Promise.resolve(undefined);
}
注意 :你不能将 async 函数的返回类型写成
string或number,必须是Promise<string>。
2. await 关键字
await 只能在 async 函数内部使用(或者在支持 Top-level await 的模块中使用)。
它的作用是:
- 暂停执行:暂停当前 async 函数的执行,直到 Promise 状态变为 resolved 或 rejected。
- 自动解包 (Unwrapping) :这是 TS 的强大之处。如果一个表达式是
Promise<string>,await之后得到的结果类型就是string。
类型解包示例
typescript
function fetchAge(): Promise<number> {
return Promise.resolve(18);
}
async function main() {
// TS 知道 fetchAge 返回 Promise<number>
// 所以 age 被自动推断为 number 类型
const age = await fetchAge();
console.log(age + 1); // 这里的加法是合法的,因为 age 是 number
}
3. 错误处理 (try...catch)
当 await 后面的 Promise 被 reject 时,会抛出一个异常。因此,标准做法是使用 try...catch。
在 TypeScript 中,catch(error) 中的 error 默认类型通常是 unknown 或 any。
typescript
async function requestData(): Promise<string> {
throw new Error("网络连接断开");
}
async function handleTask() {
try {
const data = await requestData();
console.log(data);
} catch (error) {
// 这里的 error 类型是 unknown (在 TS 4.4+ 开启 useUnknownInCatchVariables 时)
// 最佳实践:进行类型守卫
if (error instanceof Error) {
console.error("错误消息:", error.message);
} else {
console.error("未知错误:", error);
}
}
}
4. 串行 vs 并行 (性能关键)
这是使用 async/await 最容易踩的坑。await 会阻塞后续代码,如果多个请求之间没有依赖关系,不要写成串行。
❌ 串行写法 (慢)
typescript
async function serial() {
const start = Date.now();
// 假设 getUser 耗时 1s, getPosts 耗时 1s
const user = await getUser(); // 等待 1s
const posts = await getPosts(); // 再等待 1s
// 总耗时: 2s
}
✅ 并行写法 (快 - 使用 Promise.all)
typescript
async function parallel() {
// 同时启动两个任务
const userPromise = getUser();
const postsPromise = getPosts();
// 等待它们全部完成
// TS 会自动推断 results 类型为 [User, Post[]]
const [user, posts] = await Promise.all([userPromise, postsPromise]);
// 总耗时: 1s
}
5. 高级用法与技巧
5.1 异步箭头函数
typescript
const login = async (username: string): Promise<boolean> => {
return true;
};
5.2 Top-level Await (顶层 await)
在现代 TypeScript (ES2022 / ESNext 且 module 设置为 esnext/system 等) 中,可以在文件最外层直接使用 await,不需要包裹在 async function 中。这在初始化数据库连接等场景很有用。
typescript
// db.ts
import { createConnection } from 'typeorm';
// 直接等待连接建立
export const connection = await createConnection();
5.3 Promise<void> 的陷阱
有时候我们会把 async 函数作为回调传给 forEach,这通常是不对的,因为 forEach 不会等待 async 回调完成。
typescript
const ids = [1, 2, 3];
// ❌ 错误做法:forEach 不会等待 await
ids.forEach(async (id) => {
await fetchUser(id);
});
console.log('Done'); // 这行代码会在 fetchUser 完成之前就打印!
// ✅ 正确做法:使用 map + Promise.all
await Promise.all(ids.map(async (id) => {
await fetchUser(id);
}));
console.log('Done'); // 确实等待所有都完成了
总结 TS 中的 Async/Await
| 特性 | 说明 | 对应的 TS 写法 |
|---|---|---|
| 定义 | 函数必须返回 Promise | async function fn(): Promise<T> { ... } |
| 调用 | 必须在 async 函数内 | const val: T = await promise; |
| 返回值 | 自动包装 | return T 会变成 Promise<T> |
| 异常 | 使用 try-catch 捕获 | catch 块中的变量需注意类型检查 |
掌握这套机制后,你的异步代码会像同步代码一样清晰,同时拥有完整的类型检查保护。