TypeScript 开发指南 / 14 - 异步编程
异步编程
TypeScript 为 JavaScript 的异步编程提供了完整的类型支持。
Promise 类型
基本 Promise 类型
// Promise 是泛型类型,参数是 resolve 的值类型
const promise: Promise<string> = new Promise((resolve, reject) => {
setTimeout(() => resolve("Hello"), 1000);
});
// 使用
promise.then(value => {
console.log(value.toUpperCase()); // value: string
});
Promise 的三种状态
// Pending(进行中)
const pending: Promise<number> = fetchNumber();
// Fulfilled(已完成)
const fulfilled: Promise<string> = Promise.resolve("done");
// Rejected(已失败)
const rejected: Promise<never> = Promise.reject(new Error("fail"));
async function fetchNumber(): Promise<number> {
const response = await fetch("/api/number");
return response.json();
}
async / await
// async 函数返回 Promise
async function getUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
// await 等待 Promise 完成
async function main() {
try {
const user = await getUser(1);
console.log(user.name); // user: User
} catch (error) {
console.error(error);
}
}
类型推断
// 返回值类型自动推断为 Promise<string>
async function greet(name: string) {
return `Hello, ${name}!`;
}
// 等价于
async function greet(name: string): Promise<string> {
return `Hello, ${name}!`;
}
// await 的类型推断
async function example() {
const user = await getUser(1); // user: User
const data = user.name; // data: string
return data;
}
Promise 链式调用
interface User {
id: number;
name: string;
email: string;
}
interface Post {
id: number;
title: string;
userId: number;
}
// then 链式调用
fetch("/api/users/1")
.then(response => response.json()) // Promise<any>
.then((user: User) => user.id) // Promise<number>
.then(userId => fetch(`/api/posts?userId=${userId}`))
.then(response => response.json()) // Promise<any>
.then((posts: Post[]) => {
console.log(posts);
});
// 或者使用 async/await(推荐)
async function getUserPosts(userId: number): Promise<Post[]> {
const userResponse = await fetch(`/api/users/${userId}`);
const user: User = await userResponse.json();
const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
const posts: Post[] = await postsResponse.json();
return posts;
}
Promise.all
// 并行执行多个 Promise
async function fetchDashboardData() {
const [users, posts, comments] = await Promise.all([
fetch("/api/users").then(r => r.json()) as Promise<User[]>,
fetch("/api/posts").then(r => r.json()) as Promise<Post[]>,
fetch("/api/comments").then(r => r.json()) as Promise<Comment[]>
]);
return { users, posts, comments };
}
// 类型推断
const results = await Promise.all([
fetch("/api/users"),
fetch("/api/posts"),
fetch("/api/comments")
]);
// results: [Response, Response, Response]
Promise.allSettled
// 等待所有 Promise 完成(无论成功或失败)
const results = await Promise.allSettled([
fetch("/api/users"),
fetch("/api/invalid-url"),
fetch("/api/posts")
]);
results.forEach(result => {
if (result.status === "fulfilled") {
console.log("成功:", result.value);
} else {
console.log("失败:", result.reason);
}
});
Promise.race
// 返回最先完成的 Promise
async function fetchWithTimeout(
url: string,
timeout: number
): Promise<Response> {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error("Timeout")), timeout);
});
return Promise.race([fetchPromise, timeoutPromise]);
}
Promise.any
// 返回最先成功的 Promise
async function fetchFromMultipleSources(url: string): Promise<Response> {
const mirrors = [
fetch(`https://mirror1.example.com${url}`),
fetch(`https://mirror2.example.com${url}`),
fetch(`https://mirror3.example.com${url}`)
];
return Promise.any(mirrors);
}
错误处理
// try-catch 模式
async function fetchData<T>(url: string): Promise<T> {
try {
const response = await fetch(url);
if (!response.ok) {
throw new ApiError(response.status, await response.text());
}
return response.json();
} catch (error) {
if (error instanceof ApiError) {
console.error(`API Error: ${error.status}`);
} else if (error instanceof TypeError) {
console.error("网络错误");
} else {
console.error("未知错误:", error);
}
throw error;
}
}
class ApiError extends Error {
constructor(
public status: number,
public body: string
) {
super(`API Error: ${status}`);
this.name = "ApiError";
}
}
Result 模式(不抛出异常)
// 使用联合类型表示成功/失败
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
async function safeFetch<T>(url: string): Promise<Result<T>> {
try {
const response = await fetch(url);
if (!response.ok) {
return {
success: false,
error: new ApiError(response.status, await response.text())
};
}
const data = await response.json();
return { success: true, data };
} catch (error) {
return { success: false, error: error as Error };
}
}
// 使用
const result = await safeFetch<User>("/api/users/1");
if (result.success) {
console.log(result.data.name); // result.data: User
} else {
console.error(result.error.message); // result.error: Error
}
异步迭代器
// 异步生成器
async function* fetchPages(url: string): AsyncGenerator<any[]> {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
if (data.items.length === 0) {
hasMore = false;
} else {
yield data.items;
page++;
}
}
}
// 异步 for-of 循环
async function processAllPages() {
for await (const items of fetchPages("/api/users")) {
for (const item of items) {
console.log(item);
}
}
}
异步队列控制
// 限制并发数
async function asyncPool<T, R>(
concurrency: number,
items: T[],
fn: (item: T) => Promise<R>
): Promise<R[]> {
const results: R[] = [];
const executing: Promise<void>[] = [];
for (const item of items) {
const p = fn(item).then(result => {
results.push(result);
});
const e = p.then(() => {
executing.splice(executing.indexOf(e), 1);
});
executing.push(e);
if (executing.length >= concurrency) {
await Promise.race(executing);
}
}
await Promise.all(executing);
return results;
}
// 使用
const userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const users = await asyncPool(3, userIds, async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json() as Promise<User>;
});
超时与重试
// 超时控制
function timeout<T>(promise: Promise<T>, ms: number): Promise<T> {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Timeout after ${ms}ms`));
}, ms);
promise
.then(value => {
clearTimeout(timer);
resolve(value);
})
.catch(error => {
clearTimeout(timer);
reject(error);
});
});
}
// 重试机制
async function retry<T>(
fn: () => Promise<T>,
maxAttempts: number = 3,
delay: number = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
if (attempt < maxAttempts) {
await new Promise(r => setTimeout(r, delay * attempt));
}
}
}
throw lastError!;
}
// 使用
const data = await retry(
() => timeout(fetchData("/api/data"), 5000),
3,
1000
);
业务场景:数据加载状态
// 使用可辨识联合表示异步状态
type AsyncState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };
class AsyncStore<T> {
private state: AsyncState<T> = { status: "idle" };
private listeners: ((state: AsyncState<T>) => void)[] = [];
getState(): AsyncState<T> {
return this.state;
}
subscribe(listener: (state: AsyncState<T>) => void): () => void {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}
private setState(state: AsyncState<T>) {
this.state = state;
this.listeners.forEach(listener => listener(state));
}
async load(fetcher: () => Promise<T>): Promise<void> {
this.setState({ status: "loading" });
try {
const data = await fetcher();
this.setState({ status: "success", data });
} catch (error) {
this.setState({ status: "error", error: error as Error });
}
}
}
// 使用
const store = new AsyncStore<User[]>();
store.subscribe(state => {
switch (state.status) {
case "idle": console.log("等待加载"); break;
case "loading": console.log("加载中..."); break;
case "success": console.log(`加载了 ${state.data.length} 个用户`); break;
case "error": console.error(`错误: ${state.error.message}`); break;
}
});
await store.load(() => fetch("/api/users").then(r => r.json()));
注意事项
- 始终处理 Promise 错误——未捕获的 Promise 拒绝会导致难以调试的问题
- 避免 Promise 构造函数反模式——不要在 async 函数中创建 new Promise
- 注意 await 的顺序——可以并行的请求使用
Promise.all而不是串行 await - 类型断言:
fetch返回Promise<any>,需要手动断言或使用泛型 - async 函数总是返回 Promise——即使返回
undefined也是Promise<undefined>