强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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()));

注意事项

  1. 始终处理 Promise 错误——未捕获的 Promise 拒绝会导致难以调试的问题
  2. 避免 Promise 构造函数反模式——不要在 async 函数中创建 new Promise
  3. 注意 await 的顺序——可以并行的请求使用 Promise.all 而不是串行 await
  4. 类型断言fetch 返回 Promise<any>,需要手动断言或使用泛型
  5. async 函数总是返回 Promise——即使返回 undefined 也是 Promise<undefined>

扩展阅读