TypeScript 异步编程:Promise 与 async/await
TypeScript 中处理异步操作的核心工具是 Promise 和 async/await。它们帮助你避免“回调地狱”,让代码更清晰、可读性更强,同时保留类型安全。
理解 Promise 的基本结构
Promise 是一个表示异步操作最终完成或失败的对象。它有三种状态:
- pending(进行中):初始状态。
- fulfilled(已成功):操作成功完成。
- rejected(已失败):操作失败。
创建一个返回 Promise 的函数:
function fetchData(url: string): Promise<string> {
return new Promise((resolve, reject) => {
// 模拟网络请求
setTimeout(() => {
if (url.includes('error')) {
reject(new Error('请求失败'));
} else {
resolve(`数据来自 ${url}`);
}
}, 1000);
});
}
```
**调用**该函数并处理结果:
```typescript
fetchData('https://api.example.com/data')
.then(data => console.log(data))
.catch(err => console.error(err.message));
```
- `.then()` 处理成功结果。
- `.catch()` 捕获错误。
---
## 使用 async/await 简化异步逻辑
`async/await` 是 `Promise` 的语法糖,让异步代码看起来像同步代码。
**定义**一个 `async` 函数:
```typescript
async function loadUserData(userId: number): Promise<string> {
const url = `https://api.example.com/users/${userId}`;
const data = await fetchData(url);
return data;
}
- 函数前加
async,表示它会返回一个Promise。 - 在函数内部使用
await等待Promise完成。
调用 async 函数:
loadUserData(123)
.then(user => console.log('用户信息:', user))
.catch(err => console.error('加载失败:', err.message));
或者在另一个 async 函数中直接 await:
async function displayUser() {
try {
const user = await loadUserData(123);
console.log('用户信息:', user);
} catch (err) {
console.error('加载失败:', (err as Error).message);
}
}
- 务必用
try...catch包裹await调用,否则错误会变成未处理的 Promise rejection。
并发执行多个异步任务
当需要同时发起多个独立请求时,不要用 await 逐个等待,这会串行执行、浪费时间。
使用 Promise.all 并行执行:
async function fetchMultipleUrls(urls: string[]): Promise<string[]> {
const promises = urls.map(url => fetchData(url));
return await Promise.all(promises);
}
Promise.all接收一个Promise数组,返回一个新Promise。- 所有子
Promise都成功时,返回结果数组;任意一个失败,整个操作立即失败。
如果希望部分失败不影响其他任务,使用 Promise.allSettled:
async function fetchWithFallback(urls: string[]): Promise<string[]> {
const results = await Promise.allSettled(
urls.map(url => fetchData(url))
);
return results
.filter(result => result.status === 'fulfilled')
.map(result => (result as PromiseFulfilledResult<string>).value);
}
Promise.allSettled总是等到所有Promise结束,无论成功或失败。- 返回数组包含每个任务的状态和值。
错误处理的最佳实践
异步错误必须显式捕获,否则会导致程序静默失败。
避免在顶层 async 函数外遗漏 .catch():
// 危险:未处理的 rejection
displayUser();
正确做法:始终链式调用 .catch(),或在 async 函数内部使用 try...catch。
对于复杂流程,可以封装统一的错误处理逻辑:
async function safeFetch<T>(
fn: () => Promise<T>,
fallback: T
): Promise<T> {
try {
return await fn();
} catch (err) {
console.warn('操作失败,使用默认值', err);
return fallback;
}
}
// 使用
const data = await safeFetch(
() => fetchData('https://api.example.com/config'),
'{"theme":"light"}'
);
类型推导与显式标注
TypeScript 能自动推导 Promise 的返回类型,但显式标注更安全。
推荐显式声明返回类型:
// 好:明确知道返回 Promise<string>
async function getName(): Promise<string> {
return 'Alice';
}
// 避免:TypeScript 推导为 Promise<unknown>
async function badExample() {
return Math.random() > 0.5 ? 'success' : 42; // 类型不一致
}
如果异步函数可能返回不同类型的值,使用联合类型:
async function fetchEither(
flag: boolean
): Promise<string | number> {
return flag ? '文本' : 42;
}
常见陷阱与规避方法
-
忘记
await导致拿到Promise对象而非值const result = fetchData('url'); // result 是 Promise<string>,不是 string console.log(result.length); // 错误! -
在循环中顺序
await,造成性能瓶颈// 错误:串行执行 for (const url of urls) { const data = await fetchData(url); process(data); } // 正确:并行执行 const promises = urls.map(url => fetchData(url)); const results = await Promise.all(promises); results.forEach(process); -
未处理
Promiserejection
启用 TypeScript 的--strict模式,并在运行时监听未处理的 rejection:process.on('unhandledRejection', reason => { console.error('未捕获的异步错误:', reason); });
实战示例:带重试机制的 HTTP 请求
结合 Promise 和 async/await 实现一个带重试功能的请求函数:
async function fetchWithRetry(
url: string,
maxRetries: number = 3
): Promise<string> {
let lastError: Error;
for (let i = 0; i < maxRetries; i++) {
try {
return await fetchData(url);
} catch (err) {
lastError = err as Error;
console.log(`第 ${i + 1} 次尝试失败,${maxRetries - i - 1} 次重试机会`);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); // 指数退避
}
}
throw lastError!;
}
调用:
try {
const data = await fetchWithRetry('https://unstable-api.com/data');
console.log('最终获取数据:', data);
} catch (err) {
console.error('所有重试均失败:', (err as Error).message);
}
暂无评论,快来抢沙发吧!