正在加载,请稍候…

Async/Await 错误处理:你需要掌握的所有模式

掌握 JavaScript 和 TypeScript 中 async/await 的错误处理,涵盖 try-catch、Promise.allSettled、A

Async/Await 错误处理:你需要掌握的所有模式

为什么异步错误处理很棘手

同步错误会被自然地捕获并沿调用栈传播。而异步错误——即 Promise 或 async 函数内部的错误——行为不同,处理上的空白可能导致静默失败、未处理的拒绝以及难以调试的崩溃。

本指南涵盖了现代异步 JavaScript 和 TypeScript 中处理错误的每一种模式。

Async/Await 错误处理:你需要掌握的所有模式 插图

基础:使用 Async/Await 的 Try-Catch

async function fetchUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) {
      throw new Error(`HTTP ${res.status}: ${res.statusText}`);
    }
    return await res.json();
  } catch (err) {
    console.error('获取用户失败:', err);
    throw err;  // 重新抛出,以便调用者处理
  }
}

await 关键字将拒绝的 Promise "解包"为抛出的错误。如果没有 await,你操作的是 Promise 对象,错误不会被 try-catch 捕获。

常见错误:

// ❌ 错误未被捕获——缺少 await
async function buggy() {
  try {
    fetch('/api/data')  // 返回了 Promise 但未 await
      .then(res => res.json());  // 这里的拒绝不会被捕获
  } catch (err) {
    // 对于 fetch 错误,这里永远不会执行
  }
}

// ✅ 修复后
async function correct() {
  try {
    const res = await fetch('/api/data');
    return await res.json();
  } catch (err) {
    // 捕获网络错误和响应解析错误
  }
}

需要处理的错误类型

async function robustFetch(url) {
  try {
    const res = await fetch(url);

    // HTTP 错误(4xx, 5xx)默认不会抛出——你必须检查
    if (!res.ok) {
      const body = await res.text();
      throw new HttpError(res.status, res.statusText, body);
    }

    return await res.json();
  } catch (err) {
    if (err instanceof HttpError) {
      // 处理服务器错误(404, 500 等)
      throw err;
    }
    if (err instanceof TypeError && err.message.includes('fetch')) {
      // 网络错误(离线、DNS 失败、CORS)
      throw new NetworkError('网络不可用', { cause: err });
    }
    if (err instanceof SyntaxError) {
      // JSON 解析错误
      throw new ParseError('无效的 JSON 响应', { cause: err });
    }
    throw err;  // 未知错误——重新抛出
  }
}

class HttpError extends Error {
  constructor(status, statusText, body) {
    super(`HTTP ${status}: ${statusText}`);
    this.name = 'HttpError';
    this.status = status;
    this.body = body;
  }
}

Promise.all 与 Promise.allSettled

Promise.all 在任何 Promise 拒绝时立即拒绝——其他 Promise 被放弃:

// ❌ 如果任何一个请求失败,所有结果都丢失
const [users, posts, comments] = await Promise.all([
  fetchUsers(),
  fetchPosts(),
  fetchComments(),
]);

Promise.allSettled 等待所有 Promise 完成,并分别给出每个结果:

// ✅ 获取成功的结果,处理失败的结果
const results = await Promise.allSettled([
  fetchUsers(),
  fetchPosts(),
  fetchComments(),
]);

const users = results[0].status === 'fulfilled' ? results[0].value : [];
const posts = results[1].status === 'fulfilled' ? results[1].value : [];

const errors = results
  .filter(r => r.status === 'rejected')
  .map(r => r.reason);

if (errors.length > 0) {
  console.warn('部分请求失败:', errors);
}

当所有请求都必须成功时使用 Promise.all。当部分成功可接受时使用 Promise.allSettled

Async/Await 错误处理:你需要掌握的所有模式 插图

超时模式

fetch 没有内置超时。使用 AbortController

async function fetchWithTimeout(url, timeoutMs = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const res = await fetch(url, { signal: controller.signal });
    clearTimeout(timeoutId);
    return await res.json();
  } catch (err) {
    if (err.name === 'AbortError') {
      throw new Error(`请求在 ${timeoutMs}ms 后超时`);
    }
    throw err;
  } finally {
    clearTimeout(timeoutId);  // 始终清理
  }
}

或者使用 Promise.race

function timeout(ms) {
  return new Promise((_, reject) =>
    setTimeout(() => reject(new Error(`在 ${ms}ms 后超时`)), ms)
  );
}

async function fetchWithRaceTimeout(url, ms = 5000) {
  return Promise.race([
    fetch(url).then(r => r.json()),
    timeout(ms),
  ]);
}

带指数退避的重试逻辑

async function fetchWithRetry(url, options = {}) {
  const { maxRetries = 3, baseDelay = 1000, retryOn = [429, 503] } = options;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const res = await fetch(url, options);

      if (retryOn.includes(res.status) && attempt < maxRetries) {
        const delay = baseDelay * 2 ** attempt + Math.random() * 1000;
        console.log(`在 ${Math.round(delay)}ms 后重试(第 ${attempt + 1} 次)`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }

      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return await res.json();

    } catch (err) {
      if (attempt === maxRetries) throw err;

      const isRetryable = err.name === 'TypeError';  // 网络错误
      if (!isRetryable) throw err;

      const delay = baseDelay * 2 ** attempt;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

避免未处理的 Promise 拒绝

未处理的 Promise 拒绝会导致 Node.js 崩溃(自 v15 起),并在浏览器中记录警告。

// ❌ 未处理的拒绝——没有 .catch() 也没有带 try-catch 的 await
const promise = fetchData();
// promise 可能拒绝,但没有任何处理

// ✅ 处理它
fetchData().catch(err => console.error('fetchData 失败:', err));

// ✅ 或者使用 async/await
async function init() {
  try {
    await fetchData();
  } catch (err) {
    console.error('fetchData 失败:', err);
  }
}

// ✅ 全局处理程序作为最后手段
process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的拒绝:', reason);
  // 不要吞掉——记录并可选地退出
  process.exit(1);
});

Async/Await 错误处理:你需要掌握的所有模式 插图

TypeScript:类型化错误处理

TypeScript 不会为 catch 块中的错误提供类型——它们始终是 unknown

async function fetchData(url: string): Promise<Data> {
  try {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json() as Data;
  } catch (err) {
    // 在 TypeScript 严格模式下,err 是 'unknown'
    if (err instanceof Error) {
      throw new Error(`获取失败: ${err.message}`);
    }
    throw new Error('未知错误');
  }
}

Result 模式(无异常):

type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };

async function safeFetch<T>(url: string): Promise<Result<T>> {
  try {
    const res = await fetch(url);
    if (!res.ok) {
      return { ok: false, error: new Error(`HTTP ${res.status}`) };
    }
    const data = await res.json() as T;
    return { ok: true, value: data };
  } catch (err) {
    return { ok: false, error: err instanceof Error ? err : new Error(String(err)) };
  }
}

// 使用——调用处无需 try-catch
const result = await safeFetch<User>('/api/user/1');
if (!result.ok) {
  console.error('失败:', result.error.message);
  return;
}
const user = result.value;  // 类型为 User

React 中的错误边界

对于 React 组件中的异步错误,使用错误边界:

// React Query 优雅地处理异步错误
const { data, error, isLoading } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
  retry: 3,
  retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),
});

if (error) {
  return <ErrorMessage message={error.message} />;
}

快速参考

场景 解决方案
单个异步操作 try/catch 配合 await
多个操作,全部必须成功 Promise.all + try/catch
多个操作,部分失败可接受 Promise.allSettled
需要超时 AbortController + 超时
临时故障(速率限制、503) 带指数退避的重试
防止未处理的拒绝 始终 .catch() 或在 try/catchawait

→ 使用 JSON Viewer 检查 API 错误响应并理解其结构。