异步错误处理

解释 JavaScript 中异步场景的错误处理机制,包括为什么 try/catch 抓不到异步错误以及如何正确处理。

#tech / dev / frontend #type / concept #status / growing #resource / javascript #resource / ecmascript

[!info] related notes

异步错误处理

这篇笔记回答一个问题:为什么 try/catch 抓不到异步代码中的错误,以及如何正确处理。

一句话定义

try/catch 只能捕获当前同步调用栈中的异常,异步回调执行时已经不在原来的调用栈中,所以外层 try/catch 无法捕获。

核心机制

为什么 try/catch 抓不到异步错误

try {
  setTimeout(() => {
    throw new Error('catch me');
  }, 0);
} catch (e) {
  // 不会执行到这里
  console.log('caught:', e.message);
}

原因:setTimeout 回调是在未来的事件循环中执行,执行时外层 try/catch 的调用栈已经销毁了。

Promise 中的错误处理

Promise 有自己的错误处理机制,通过 .catch() 捕获。

Promise.reject(new Error('promise error'))
  .catch(e => console.log('caught:', e.message));

async/await 中的错误处理

async/await 允许用 try/catch 包裹异步调用,因为 await 会暂停当前 async 函数,等 Promise settle 后恢复。

async function fetchData() {
  try {
    const res = await fetch('/api/data');
    if (!res.ok) throw new Error('network error');
    return await res.json();
  } catch (e) {
    console.log('caught:', e.message);
  }
}

各类异步场景的错误处理

setTimeout / setInterval

// 错误做法
try {
  setTimeout(() => { throw new Error('oops'); }, 0);
} catch (e) {} // 抓不到

// 正确做法:在回调内部处理
setTimeout(() => {
  try {
    // 可能出错的逻辑
  } catch (e) {
    console.error(e);
  }
}, 0);

事件监听器

// 错误做法
try {
  button.addEventListener('click', () => { throw new Error('click error'); });
} catch (e) {} // 抓不到

// 正确做法
button.addEventListener('click', () => {
  try {
    // 事件处理逻辑
  } catch (e) {
    console.error(e);
  }
});

Promise.all 中的错误

Promise.all 只要有一个 reject 就会整体 reject。

try {
  const [a, b] = await Promise.all([
    fetch('/api/a'),
    fetch('/api/b'),
  ]);
} catch (e) {
  // 任意一个请求失败都会进入这里
  console.error('one of the requests failed:', e);
}

多个 fetch 都完成后再继续

// 用 Promise.all 等待所有请求完成
const results = await Promise.all([
  fetch('/api/users').then(r => r.json()),
  fetch('/api/posts').then(r => r.json()),
]);

// 如果不在意个别失败,可以用 Promise.allSettled
const settled = await Promise.allSettled([
  fetch('/api/users').then(r => r.json()),
  fetch('/api/posts').then(r => r.json()),
]);

全局错误捕获

// 未处理的 Promise rejection
window.addEventListener('unhandledrejection', e => {
  console.error('unhandled rejection:', e.reason);
  e.preventDefault(); // 防止控制台报错
});

// 未捕获的同步错误
window.addEventListener('error', e => {
  console.error('uncaught error:', e.error);
});

常见边界

  • await 只能捕获 await 表达式本身的 reject,不能捕获后续同步代码的 throw(虽然一般 try/catch 也能抓到)
  • Promise 链中如果中间有 .catch() 处理了错误,后续的 .catch() 不会触发
  • Node.js 中还有 process.on(‘uncaughtException’) 和 process.on(‘unhandledRejection’)

最小例子

// 错误:try/catch 抓不到异步错误
try {
  setTimeout(() => { throw new Error('oops'); }, 0);
} catch (e) { /* 抓不到 */ }

// 正确:Promise + catch
fetch('/api/data')
  .then(r => r.json())
  .catch(e => console.error(e));

// 正确:async/await + try/catch
async function getData() {
  try {
    const res = await fetch('/api/data');
    return await res.json();
  } catch (e) {
    console.error(e);
  }
}
创建于 2026/4/3 更新于 2026/5/27