JavaScript 里 Promise 链中 then 回调抛错后,后面的 catch 为什么有时接不住?
写业务代码时经常遇到这种状况:一个接口请求失败后,你在 `.then()` 里做了错误处理,但代码还是走到了全局的 `unhandledrejection` 事件,控制台飘红。或者反过来,明明某个 `.then()` 里 `throw new Error()` 了,后面的 `.catch()` 却像没看见一样,最后错误跑到了另一个分支里。这不是 Promise 实现有 bug,而是很多人对 then 链的返回值传递和错误冒泡机制理解得不够完整。Promise 的错误处理依赖链式调用中每个环节的返回状态,一旦在某个 `then` 里把 rejection 消化掉,或者返回了一个新的 Promise 把错误吞了,后续的 catch 就永远接不到。这篇文章会从几个真实的代码片段出发,梳理 then 链里错误是怎么一级一级往下传的,以及为什么有时 catch 写了却没生效。 ## 一个容易踩的坑:在 then 里 return 了新的 Promise 先看一段常见的登录逻辑: ```js function login(username, password) { return fetch('/api/login', { method: 'POST', body: JSON.stringify({ username, password }) }) .then(res => res.json()) .then(data => { if (data.code !== 0) { // 这里只是返回了一个 rejected 的 Promise return Promise.reject(new Error(data.message)) } return data.token }) .catch(err => { console.error('login failed:', err) throw err }) } login('foo', 'bar') .then(token => { // 期望在这里拿到 token }) .catch(err => { // 期望在这里处理登录错误 }) ``` 这段代码本身没有大问题,`login` 内部的 `.catch` 把错误重新抛出去,所以外层的 `.catch` 能接到。但很多人在写的时候会在 `catch` 里返回 `false` 或者一个默认值,比如: ```js .catch(err => { console.error(err) return null }) ``` 这个时候链式调用的状态从 rejected 变成了 fulfilled,外层的 `.then(token => ...)` 会被执行,token 是 `null`,而外层后面的 `.catch` 根本不会触发。如果你没在 `then` 里检查 token 是否为空,后续逻辑就可能把 null 传下去,引发更难排查的问题。 ## 错误在 then 链里的传递规则 理解错误传递,只要记住两件事: 1. `.then(onFulfilled, onRejected)` 里如果 `onFulfilled` 抛出异常,返回的 Promise 会变成 rejected;如果 `onRejected` 抛出异常,同样变成 rejected;如果 `onRejected` 返回一个普通值,Promise 会变成 fulfilled。 2. `.catch(onRejected)` 本质上是 `.then(undefined, onRejected)`,所以它也只能捕获它前面那个 Promise 的 rejection。 看一个对比: ```js Promise.reject(new Error('a')) .then(() => console.log('then')) .catch(err => { console.log('catch1', err.message) // catch1 a // 没有 throw,返回 undefined }) .catch(err => { console.log('catch2', err.message) // 不会执行 }) ``` 第一个 `catch` 返回了 `undefined`,相当于把错误处理掉了,后面就没有 rejection 再往下传。如果希望后面的 catch 还能拿到,就必须在 catch 里重新 throw: ```js Promise.reject(new Error('a')) .catch(err => { console.log('catch1', err.message) throw err }) .catch(err => { console.log('catch2', err.message) // catch2 a }) ``` 这个规则本身不复杂,但在嵌套 Promise 或者多个 then 串联时容易混乱,因为每个 then 都会产生一个新的 Promise。 ## 嵌套 Promise 里的错误不会自动冒泡到外层链 有时候我们会这样写: ```js fetch('/api/user') .then(res => res.json()) .then(user => { // 又发起一个请求,但没有 return fetch(`/api/orders/${user.id}`) .then(res => res.json()) .then(orders => { renderOrders(orders) }) }) .catch(err => { console.error('something wrong', err) }) ``` 如果 `/api/orders` 那个请求失败了,内层的 Promise 会 reject,但因为没有 `return`,外层链根本不知道这个新 Promise 的存在。内层的 rejection 会变成一个未处理的 Promise rejection,触发 `unhandledrejection`,而外层的 `.catch` 完全接不到。 正确的做法是把这个内层 Promise 返回出去,让链式调用保持扁平: ```js fetch('/api/user') .then(res => res.json()) .then(user => { return fetch(`/api/orders/${user.id}`) .then(res => res.json()) }) .then(orders => { renderOrders(orders) }) .catch(err => { console.error('something wrong', err) }) ``` 这样内层的错误才会沿着 Promise 链传到外层 catch。 ## async/await 里同样的逻辑 用 `async/await` 时,错误传递规则没变,只是写法不同。`await` 一个 rejected Promise 会抛出异常,可以用 `try/catch` 捕获: ```js async function loadUser() { try { const res = await fetch('/api/user') const user = await res.json() return user } catch (err) { console.error('load user failed', err) throw err } } ``` 如果在 `catch` 里没有 `throw err`,那么 `loadUser` 返回的 Promise 会变成 fulfilled,调用方用 `.catch` 是接不到的。这和前面 `.then` 链的行为一致。 还有一个容易忽略的点:在 `async` 函数里,如果你在 `await` 之后同步抛出一个错误,和 `await` 一个 rejected Promise 一样,都会让函数返回 rejected Promise。 ```js async function getData() { const res = await fetch('/api/data') if (!res.ok) { throw new Error('request failed') // 函数返回 rejected Promise } return res.json() } getData() .then(data => console.log(data)) .catch(err => console.error(err)) // 能接住 ``` 但如果调用方没有写 catch,并且也没有用 `try/catch` 包住 await,错误就会变成 `unhandledrejection`。 ## 什么时候该重新 throw,什么时候该消化错误 这其实是一个业务决策,不是语法问题。判断标准很简单:当前这个 catch 能不能把错误处理完整? 比如在某个子流程里,请求配置失败,但可以降级到本地缓存,这种错误就应该被消化掉,不要往外抛: ```js async function getConfig() { try { const res = await fetch('/api/config') return await res.json() } catch (err) { console.warn('remote config failed, fallback to local') return localConfig } } ``` 如果当前层只是记录日志,并不具备恢复能力,就应该把错误继续抛出去,让上层决定怎么处理: ```js async function updateProfile(data) { try { await api.put('/profile', data) } catch (err) { logError(err) throw err // 让调用方知道更新失败 } } ``` 最怕的是在中间层既没有恢复,又默默返回了 `undefined`,导致上层拿到一个“成功但没数据”的结果,后续逻辑全乱。 ## 调试 unhandledrejection 的一个实用手段 Chrome DevTools 的 Console 里可以打开 “Pause on caught exceptions” 和 “Pause on uncaught exceptions”,配合 `window.addEventListener('unhandledrejection', event => { ... })` 来定位是哪个 Promise 没有被处理。 ```js window.addEventListener('unhandledrejection', event => { // event.reason 是 rejection 的原因 console.error('Unhandled rejection:', event.reason) // 可以在这里上报监控 }) ``` 在 Node.js 里类似: ```js process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled rejection at:', promise, 'reason:', reason) }) ``` 但要注意,`unhandledrejection` 只在 rejection 发生后没有任何处理程序时才会触发。如果在一个 Promise 之后很晚才加上 catch,浏览器可能已经触发过这个事件了。所以生产环境里更推荐在 Promise 链的末端统一处理,而不是依赖全局事件兜底。 ## 一个实际场景:多个并发请求里某个失败 用 `Promise.all` 时,只要有一个 Promise reject,整体就会 reject,其他 Promise 的结果会被忽略。如果想让每个请求独立处理错误,可以用 `Promise.allSettled`: ```js const results = await Promise.allSettled([ fetch('/api/a').then(res => res.json()), fetch('/api/b').then(res => res.json()), fetch('/api/c').then(res => res.json()) ]) results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`request ${index} success:`, result.value) } else { console.error(`request ${index} failed:`, result.reason) } }) ``` 如果坚持用 `Promise.all`,就需要在单个请求的 catch 里决定是重新抛出让整体失败,还是返回一个占位值让整体继续。两种选择没有对错,取决于业务是否允许部分数据缺失。 Promise 的错误处理看起来只是 `.catch` 的位置问题,实际写起来会因为返回值、嵌套、async 函数的隐式转换产生很多分支。遇到 catch 接不住的情况,先检查前一个环节是不是把错误消化了,再检查有没有漏掉 return。多数时候问题就在这两处。 推荐查看天气:https://www.0dtq.com/dongtou/ 推荐查看天气:https://www.0dtq.com/dongtou/zuotian.html 推荐查看天气:https://www.0dtq.com/dongtou/7tian.html 推荐查看天气:https://www.0dtq.com/dongtou/10tian.html 推荐查看天气:https://www.0dtq.com/dongtou/15tian.html 推荐查看天气:https://www.0dtq.com/dongtou/40tian.html 推荐查看天气:https://www.0dtq.com/yongjia/ 推荐查看天气:https://www.0dtq.com/yongjia/zuotian.html 推荐查看天气:https://www.0dtq.com/yongjia/7tian.html 推荐查看天气:https://www.0dtq.com/yongjia/10tian.html 推荐查看天气:https://www.0dtq.com/yongjia/15tian.html 推荐查看天气:https://www.0dtq.com/yongjia/40tian.html 推荐查看天气:https://www.0dtq.com/pingyang/ 推荐查看天气:https://www.0dtq.com/pingyang/zuotian.html 推荐查看天气:https://www.0dtq.com/pingyang/7tian.html 推荐查看天气:https://www.0dtq.com/pingyang/10tian.html 推荐查看天气:https://www.0dtq.com/pingyang/15tian.html 推荐查看天气:https://www.0dtq.com/pingyang/40tian.html 推荐查看天气:https://www.0dtq.com/cangnan/ 推荐查看天气:https://www.0dtq.com/cangnan/zuotian.html 推荐查看天气:https://www.0dtq.com/cangnan/7tian.html 推荐查看天气:https://www.0dtq.com/cangnan/10tian.html 推荐查看天气:https://www.0dtq.com/cangnan/15tian.html 推荐查看天气:https://www.0dtq.com/cangnan/40tian.html 推荐查看天气:https://www.0dtq.com/wencheng/ 推荐查看天气:https://www.0dtq.com/wencheng/zuotian.html 推荐查看天气:https://www.0dtq.com/wencheng/7tian.html 推荐查看天气:https://www.0dtq.com/wencheng/10tian.html 推荐查看天气:https://www.0dtq.com/wencheng/15tian.html 推荐查看天气:https://www.0dtq.com/wencheng/40tian.html 推荐查看天气:https://www.0dtq.com/taishun/ 推荐查看天气:https://www.0dtq.com/taishun/zuotian.html 推荐查看天气:https://www.0dtq.com/taishun/7tian.html 推荐查看天气:https://www.0dtq.com/taishun/10tian.html 推荐查看天气:https://www.0dtq.com/taishun/15tian.html 推荐查看天气:https://www.0dtq.com/taishun/40tian.html 推荐查看天气:https://www.0dtq.com/ruian/ 推荐查看天气:https://www.0dtq.com/ruian/zuotian.html 推荐查看天气:https://www.0dtq.com/ruian/7tian.html 推荐查看天气:https://www.0dtq.com/ruian/10tian.html 推荐查看天气:https://www.0dtq.com/ruian/15tian.html 推荐查看天气:https://www.0dtq.com/ruian/40tian.html 推荐查看天气:https://www.0dtq.com/leqing/ 推荐查看天气:https://www.0dtq.com/leqing/zuotian.html 推荐查看天气:https://www.0dtq.com/leqing/7tian.html 推荐查看天气:https://www.0dtq.com/leqing/10tian.html 推荐查看天气:https://www.0dtq.com/leqing/15tian.html 推荐查看天气:https://www.0dtq.com/leqing/40tian.html 推荐查看天气:https://www.0dtq.com/jiaxing/ 推荐查看天气:https://www.0dtq.com/jiaxing/zuotian.html 推荐查看天气:https://www.0dtq.com/jiaxing/7tian.html 推荐查看天气:https://www.0dtq.com/jiaxing/10tian.html 推荐查看天气:https://www.0dtq.com/jiaxing/15tian.html 推荐查看天气:https://www.0dtq.com/jiaxing/40tian.html 推荐查看天气:https://www.0dtq.com/xiuzhou/ 推荐查看天气:https://www.0dtq.com/xiuzhou/zuotian.html 推荐查看天气:https://www.0dtq.com/xiuzhou/7tian.html 推荐查看天气:https://www.0dtq.com/xiuzhou/10tian.html 推荐查看天气:https://www.0dtq.com/xiuzhou/15tian.html 推荐查看天气:https://www.0dtq.com/xiuzhou/40tian.html 推荐查看天气:https://www.0dtq.com/jiashan/ 推荐查看天气:https://www.0dtq.com/jiashan/zuotian.html 推荐查看天气:https://www.0dtq.com/jiashan/7tian.html 推荐查看天气:https://www.0dtq.com/jiashan/10tian.html 推荐查看天气:https://www.0dtq.com/jiashan/15tian.html 推荐查看天气:https://www.0dtq.com/jiashan/40tian.html 推荐查看天气:https://www.0dtq.com/jiaxinghaiyan/ 推荐查看天气:https://www.0dtq.com/jiaxinghaiyan/zuotian.html 推荐查看天气:https://www.0dtq.com/jiaxinghaiyan/7tian.html 推荐查看天气:https://www.0dtq.com/jiaxinghaiyan/10tian.html 推荐查看天气:https://www.0dtq.com/jiaxinghaiyan/15tian.html 推荐查看天气:https://www.0dtq.com/jiaxinghaiyan/40tian.html 推荐查看天气:https://www.0dtq.com/haining/ 推荐查看天气:https://www.0dtq.com/haining/zuotian.html 推荐查看天气:https://www.0dtq.com/haining/7tian.html 推荐查看天气:https://www.0dtq.com/haining/10tian.html 推荐查看天气:https://www.0dtq.com/haining/15tian.html 推荐查看天气:https://www.0dtq.com/haining/40tian.html 推荐查看天气:https://www.0dtq.com/pinghu/ 推荐查看天气:https://www.0dtq.com/pinghu/zuotian.html 推荐查看天气:https://www.0dtq.com/pinghu/7tian.html 推荐查看天气:https://www.0dtq.com/pinghu/10tian.html 推荐查看天气:https://www.0dtq.com/pinghu/15tian.html 推荐查看天气:https://www.0dtq.com/pinghu/40tian.html 推荐查看天气:https://www.0dtq.com/tongxiang/ 推荐查看天气:https://www.0dtq.com/tongxiang/zuotian.html 推荐查看天气:https://www.0dtq.com/tongxiang/7tian.html 推荐查看天气:https://www.0dtq.com/tongxiang/10tian.html 推荐查看天气:https://www.0dtq.com/tongxiang/15tian.html 推荐查看天气:https://www.0dtq.com/tongxiang/40tian.html 推荐查看天气:https://www.0dtq.com/huzhou/ 推荐查看天气:https://www.0dtq.com/huzhou/zuotian.html 推荐查看天气:https://www.0dtq.com/huzhou/7tian.html 推荐查看天气:https://www.0dtq.com/huzhou/10tian.html 推荐查看天气:https://www.0dtq.com/huzhou/15tian.html 推荐查看天气:https://www.0dtq.com/huzhou/40tian.html 推荐查看天气:https://www.0dtq.com/wuxing/ 推荐查看天气:https://www.0dtq.com/wuxing/zuotian.html 推荐查看天气:https://www.0dtq.com/wuxing/7tian.html 推荐查看天气:https://www.0dtq.com/wuxing/10tian.html 推荐查看天气:https://www.0dtq.com/wuxing/15tian.html 推荐查看天气:https://www.0dtq.com/wuxing/40tian.html 推荐查看天气:https://www.0dtq.com/nanxun/ 推荐查看天气:https://www.0dtq.com/nanxun/zuotian.html 推荐查看天气:https://www.0dtq.com/nanxun/7tian.html 推荐查看天气:https://www.0dtq.com/nanxun/10tian.html 推荐查看天气:https://www.0dtq.com/nanxun/15tian.html 推荐查看天气:https://www.0dtq.com/nanxun/40tian.html 推荐查看天气:https://www.0dtq.com/deqingxian/ 推荐查看天气:https://www.0dtq.com/deqingxian/zuotian.html 推荐查看天气:https://www.0dtq.com/deqingxian/7tian.html 推荐查看天气:https://www.0dtq.com/deqingxian/10tian.html 推荐查看天气:https://www.0dtq.com/deqingxian/15tian.html 推荐查看天气:https://www.0dtq.com/deqingxian/40tian.html 推荐查看天气:https://www.0dtq.com/changxing/ 推荐查看天气:https://www.0dtq.com/changxing/zuotian.html 推荐查看天气:https://www.0dtq.com/changxing/7tian.html 推荐查看天气:https://www.0dtq.com/changxing/10tian.html 推荐查看天气:https://www.0dtq.com/changxing/15tian.html 推荐查看天气:https://www.0dtq.com/changxing/40tian.html 推荐查看天气:https://www.0dtq.com/anji/ 推荐查看天气:https://www.0dtq.com/anji/zuotian.html 推荐查看天气:https://www.0dtq.com/anji/7tian.html 推荐查看天气:https://www.0dtq.com/anji/10tian.html 推荐查看天气:https://www.0dtq.com/anji/15tian.html 推荐查看天气:https://www.0dtq.com/anji/40tian.html 推荐查看天气:https://www.0dtq.com/shaoxing/ 推荐查看天气:https://www.0dtq.com/shaoxing/zuotian.html 推荐查看天气:https://www.0dtq.com/shaoxing/7tian.html 推荐查看天气:https://www.0dtq.com/shaoxing/10tian.html 推荐查看天气:https://www.0dtq.com/shaoxing/15tian.html 推荐查看天气:https://www.0dtq.com/shaoxing/40tian.html 推荐查看天气:https://www.0dtq.com/yuecheng/ 推荐查看天气:https://www.0dtq.com/yuecheng/zuotian.html 推荐查看天气:https://www.0dtq.com/yuecheng/7tian.html 推荐查看天气:https://www.0dtq.com/yuecheng/10tian.html 推荐查看天气:https://www.0dtq.com/yuecheng/15tian.html 推荐查看天气:https://www.0dtq.com/yuecheng/40tian.html 推荐查看天气:https://www.0dtq.com/xinchang/ 推荐查看天气:https://www.0dtq.com/xinchang/zuotian.html 推荐查看天气:https://www.0dtq.com/xinchang/7tian.html 推荐查看天气:https://www.0dtq.com/xinchang/10tian.html 推荐查看天气:https://www.0dtq.com/xinchang/15tian.html 推荐查看天气:https://www.0dtq.com/xinchang/40tian.html 推荐查看天气:https://www.0dtq.com/zhuji/ 推荐查看天气:https://www.0dtq.com/zhuji/zuotian.html 推荐查看天气:https://www.0dtq.com/zhuji/7tian.html 推荐查看天气:https://www.0dtq.com/zhuji/10tian.html 推荐查看天气:https://www.0dtq.com/zhuji/15tian.html 推荐查看天气:https://www.0dtq.com/zhuji/40tian.html 推荐查看天气:https://www.0dtq.com/shangyu/ 推荐查看天气:https://www.0dtq.com/shangyu/zuotian.html 推荐查看天气:https://www.0dtq.com/shangyu/7tian.html 推荐查看天气:https://www.0dtq.com/shangyu/10tian.html 推荐查看天气:https://www.0dtq.com/shangyu/15tian.html 推荐查看天气:https://www.0dtq.com/shangyu/40tian.html 推荐查看天气:https://www.0dtq.com/chengzhou/ 推荐查看天气:https://www.0dtq.com/chengzhou/zuotian.html 推荐查看天气:https://www.0dtq.com/chengzhou/7tian.html 推荐查看天气:https://www.0dtq.com/chengzhou/10tian.html 推荐查看天气:https://www.0dtq.com/chengzhou/15tian.html 推荐查看天气:https://www.0dtq.com/chengzhou/40tian.html 推荐查看天气:https://www.0dtq.com/jinhua/ 推荐查看天气:https://www.0dtq.com/jinhua/zuotian.html 推荐查看天气:https://www.0dtq.com/jinhua/7tian.html 推荐查看天气:https://www.0dtq.com/jinhua/10tian.html 推荐查看天气:https://www.0dtq.com/jinhua/15tian.html 推荐查看天气:https://www.0dtq.com/jinhua/40tian.html 推荐查看天气:https://www.0dtq.com/jindong/ 推荐查看天气:https://www.0dtq.com/jindong/zuotian.html 推荐查看天气:https://www.0dtq.com/jindong/7tian.html 推荐查看天气:https://www.0dtq.com/jindong/10tian.html 推荐查看天气:https://www.0dtq.com/jindong/15tian.html 推荐查看天气:https://www.0dtq.com/jindong/40tian.html 推荐查看天气:https://www.0dtq.com/wuyi/ 推荐查看天气:https://www.0dtq.com/wuyi/zuotian.html 推荐查看天气:https://www.0dtq.com/wuyi/7tian.html 推荐查看天气:https://www.0dtq.com/wuyi/10tian.html 推荐查看天气:https://www.0dtq.com/wuyi/15tian.html 推荐查看天气:https://www.0dtq.com/wuyi/40tian.html 推荐查看天气:https://www.0dtq.com/jinhuapujiang/ 推荐查看天气:https://www.0dtq.com/jinhuapujiang/zuotian.html 推荐查看天气:https://www.0dtq.com/jinhuapujiang/7tian.html 推荐查看天气:https://www.0dtq.com/jinhuapujiang/10tian.html 推荐查看天气:https://www.0dtq.com/jinhuapujiang/15tian.html 推荐查看天气:https://www.0dtq.com/jinhuapujiang/40tian.html 推荐查看天气:https://www.0dtq.com/panan/ 推荐查看天气:https://www.0dtq.com/panan/zuotian.html 推荐查看天气:https://www.0dtq.com/panan/7tian.html 推荐查看天气:https://www.0dtq.com/panan/10tian.html 推荐查看天气:https://www.0dtq.com/panan/15tian.html 推荐查看天气:https://www.0dtq.com/panan/40tian.html 推荐查看天气:https://www.0dtq.com/lanxi/ 推荐查看天气:https://www.0dtq.com/lanxi/zuotian.html 推荐查看天气:https://www.0dtq.com/lanxi/7tian.html 推荐查看天气:https://www.0dtq.com/lanxi/10tian.html 推荐查看天气:https://www.0dtq.com/lanxi/15tian.html 推荐查看天气:https://www.0dtq.com/lanxi/40tian.html 推荐查看天气:https://www.0dtq.com/yiwu/ 推荐查看天气:https://www.0dtq.com/yiwu/zuotian.html