DrillLab
第 43 / 105 道43 / 105 · #310

错误处理怎么做

Error Handling

先自己答,再往下看Answer it yourself first

一句话:同步用try/catch, Promise 用 .catch()async/awaittry/catch最上层要有兜底

最重要的一条:try/catch抓不到「回调里」的异步错误。因为 setTimeout的回调是在另一轮事件循环里执行的, 那时 try 块早就出栈了。

四个层次的实践:

  • 该抛就抛—— 别把错误吞掉换成return null, 调用方无法区分「没有」和「出错了」。
  • Error 对象, 别抛字符串 —— 否则没有堆栈。 需要区分类型就class NotFoundError extends Error
  • 只在能处理的地方 catch。 catch 了却什么都不做(catch (e) {}) 是最坏的写法。
  • 兜底—— 浏览器 window.onerror +unhandledrejection; Node process.on("uncaughtException"); React 用错误边界(见 #333); Express 用错误中间件

会追问:「fetch 的错误怎么处理?」——陷阱题fetch 只在网络层失败时 reject,404 / 500 是 resolve 的, 必须自己检查 res.ok。 这也是 React 那门课里 fetch 变式题的第一个考点。

In one line: try/catch for synchronous code, .catch() for Promises, try/catch again for async/await; and always keep a backstop at the very top.

The most important point: try/catch cannot catch an async error thrown inside a callback. A setTimeout callback runs in a later turn of the event loop, and by then the try block is long off the stack.

Four levels of practice:

  • Throw when you should throw — do not swallow the error and hand back return null; the caller then cannot tell “not there” from “it broke”.
  • Throw an Error object, never a string — a string carries no stack. When callers need to tell cases apart, write class NotFoundError extends Error.
  • Only catch where you can actually handle it. Catching and then doing nothing (catch (e) {}) is the worst thing you can write.
  • Keep a backstop — in the browser window.onerror plus unhandledrejection; in Node process.on("uncaughtException"); in React an error boundary (see #333); in Express error-handling middleware.

Follow-up: “How do you handle errors from fetch?” — this is a trick question. fetch only rejects when the network layer fails; 404 and 500 both resolve, so you have to check res.ok yourself. That is also the first thing the fetch variant question in the React course tests.

JavaScript四条实践Four practices示意Illustrative
1// ✗ 抓不到 —— 回调在下一轮事件循环里跑
2try {
3 setTimeout(() => { throw new Error("炸了"); }, 0);
4} catch (e) {
5 console.log("抓不到这里");
6}
7
8// ✓ 异步错误要在异步链里抓
9try {
10 await somethingAsync();
11} catch (e) { /* ✓ */ }
12
13// ✗ 吞掉错误,调用方分不清「没有」还是「出错」
14async function getUser(id) {
15 try { return await api.get(id); }
16 catch { return null; }
17}
18
19// ✓ 让它抛,或者抛一个带类型的错误
20class NotFoundError extends Error {}
21if (!row) throw new NotFoundError(`user ${id} 不存在`);
22
23// 兜底
24window.addEventListener("unhandledrejection", (e) => report(e.reason));
1// ✗ Never caught —— the callback runs in a later turn of the event loop
2try {
3 setTimeout(() => { throw new Error("failed"); }, 0);
4} catch (e) {
5 console.log("this line is never reached");
6}
7
8// ✓ Catch an async error inside the async chain
9try {
10 await somethingAsync();
11} catch (e) { /* ✓ */ }
12
13// ✗ Swallowing the error: the caller cannot tell "not there" from "it failed"
14async function getUser(id) {
15 try { return await api.get(id); }
16 catch { return null; }
17}
18
19// ✓ Let it throw, or throw an error that has a type
20class NotFoundError extends Error {}
21if (!row) throw new NotFoundError(`user ${id} not found`);
22
23// A last line of defence
24window.addEventListener("unhandledrejection", (e) => report(e.reason));