Node.js 中如何处理 Uncaught Exception (未捕获异常) 和 Unhandled Promise Rejection (未处理的 Promise 拒绝)?

各位好,欢迎来到今天的“Node.js 异常处理生存指南”讲座。我是你们今天的导游,带大家探索 Node.js 异常处理的奇妙世界,保证让大家满载而归,再也不怕那些神秘莫测的 Uncaught Exception 和 Unhandled Promise Rejection。

准备好了吗? 让我们开始吧!

第一站:认识你的敌人 – Uncaught Exception 和 Unhandled Promise Rejection

首先,我们要搞清楚这两个家伙到底是什么来头。想象一下,你写了一段代码,结果它突然崩溃了,控制台里冒出一堆红字,告诉你出现了 "Uncaught Exception" 或者 "Unhandled Promise Rejection"。 别慌,这并不是世界末日,只是你的代码里出了点小问题,Node.js 正在告诉你。

  • Uncaught Exception (未捕获异常): 这指的是在你的代码里,抛出了一个异常,但是没有任何 try...catch 语句来捕获它。 就像一个不受控制的熊孩子,到处乱跑,最后撞坏了东西。

    function dangerousFunction() {
      throw new Error("Boom!");
    }
    
    dangerousFunction(); // 
<think>我们正在解决一个具体问题:在Node.js中间件中,使用`res.send`发送响应后,前端却显示"Uncaught (in promise)"错误。这个错误通常发生在异步操作中,比如前端使用fetch API调用接口,而接口返回的响应在Promise链中被视为异常。 原因分析: 1. 后端发送的响应状态码不在200-299范围内(例如500、400等),而前端使用fetch API时没有处理非成功的响应状态。 2. 后端在发送响应后,代码继续执行并抛出错误(例如在`res.send`之后还有代码抛出错误),导致前端捕获到异常。 3. 后端没有正确设置响应格式,前端解析响应时出错。 解决方案: 一、后端确保正确发送响应 1. 确保在发送响应后不再执行代码(使用return提前退出): ```javascript app.get('/data', (req, res) => { // ...处理逻辑 if (error) { return res.status(500).json({ error: 'Server error' }); } res.status(200).json(data); // 正常发送 // 注意:不要在此后写任何代码,除非有return }); ``` 2. 处理异步操作中的错误: ```javascript app.get('/async', async (req, res, next) => { try { const data = await someAsyncOperation(); res.json(data); } catch (err) { // 捕获异步操作中的错误,并发送错误响应 res.status(500).json({ error: err.message }); } }); ``` 3. 避免在发送响应后抛出错误: 如果发送响应后还有代码执行,并且可能抛出错误,需要确保这些错误被捕获并不再发送响应(因为响应已经发送了)。可以使用条件判断`res.headersSent`: ```javascript app.get('/example', (req, res) => { res.send('OK'); // 后续操作 if (someCondition) { // 但注意:响应已经发送,不能再调用res.send // 所以这里只能记录错误,不能发送响应 console.error('Something happened after response'); } }); ``` 二、前端正确处理响应 前端在调用API时,需要处理非2xx的响应状态码: 1. 使用fetch API时,需要检查响应状态: ```javascript fetch('/api/data') .then(response => { if (!response.ok) { // 如果响应状态码不是200-299,抛出错误 throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { console.log(data); }) .catch(error => { console.error('Error:', error); // 在这里处理错误,例如显示错误信息给用户 }); ``` 2. 使用async/await: ```javascript async function fetchData() { try { const response = await fetch('/api/data'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log(data); } catch (error) { console.error('Fetch error:', error); } } ``` 三、后端统一错误处理中间件 确保后端有一个错误处理中间件,捕获所有未处理的错误,并发送一致的错误响应格式: ```javascript // 在路由之后注册错误处理中间件 app.use((err, req, res, next) => { // 如果响应头已经发送,则不再发送 if (res.headersSent) { return next(err); } // 设置错误状态码,默认为500 const statusCode = err.statusCode || 500; // 发送错误响应 res.status(statusCode).json({ error: { message: err.message || 'Internal Server Error', }, }); }); ``` 这样,当后端发生错误时,会通过这个中间件发送错误响应,前端就能捕获到结构化的错误信息。 四、确保不要多次发送响应 在中间件中,确保每个请求只发送一次响应。使用`return`来避免执行后续代码。 总结: 1. 后端:确保每个请求只发送一次响应,正确处理异步错误,使用错误处理中间件。 2. 前端:检查响应状态码,处理非2xx的响应。 通过以上步骤,应该能解决前端显示"Uncaught (in promise)"错误的问题。 如果问题仍然存在,请检查: - 后端代码是否有未捕获异常(可以使用`process.on('unhandledRejection', ...)``process.on('uncaughtException', ...)`监听) - 网络请求是否因为超时或跨域问题而失败(跨域问题会导致前端无法获取响应,从而触发错误) 下面我们生成相关问题:</think>### 解决 Node.js 中间件使用 `res.send` 后前端显示 "Uncaught (in promise)" 错误 #### 问题根本原因 1. **前端未处理非 200 响应** 当后端返回非 200 状态码(如 400/500)时,前端使用 `fetch` 或 `axios` 未正确处理异常响应[^1] ```javascript // 前端错误示例:未处理错误状态 fetch('/api/data').then(res => res.json()).then(console.log) ``` 2. **后端响应格式不一致** 错误响应未返回结构化 JSON 数据,导致前端解析失败[^2] ```javascript // 后端错误示例:直接发送字符串 res.status(400).send('Invalid input') ``` 3. **异步操作未处理错误** 后端在 `res.send` 后仍有异步操作抛出未捕获异常[^3] ```javascript app.post('/submit', async (req, res) => { res.send({ success: true }) // 响应已发送 await cleanupOperation() // 后续异步操作可能出错 }) ``` --- ### 完整解决方案 #### 1. 后端统一错误响应格式 ```javascript // 错误处理中间件 app.use((err, req, res, next) => { const status = err.status || 500 res.status(status).json({ // 确保返回结构化 JSON error: { code: status, message: err.message || 'Internal Server Error', details: process.env.NODE_ENV === 'development' ? err.stack : undefined } }) }) // 路由中使用 app.get('/data', (req, res) => { try { // 业务逻辑... return res.json({ data: result }) // 使用 return 终止执行 } catch (err) { err.status = 400 // 设置错误状态码 next(err) // 传递给错误处理中间件 } }) ``` #### 2. 前端正确处理异常响应 ```javascript // 使用 fetch async function fetchData() { try { const response = await fetch('/api/data') if (!response.ok) { // 检查状态码 const errorData = await response.json() throw new Error(errorData.error.message) } return await response.json() } catch (err) { // 显示用户友好错误提示 showNotification(`请求失败: ${err.message}`) } } // 使用 axios axios.get('/api/data').catch(error => { if (error.response) { // 后端返回的结构化错误 console.error('服务器错误:', error.response.data.error.message) } else { console.error('网络错误:', error.message) } }) ``` #### 3. 后端确保响应后终止执行 ```javascript app.post('/submit', async (req, res, next) => { try { const result = await db.insert(req.body) // 方法1:使用 return 确保终止 return res.status(201).json(result) // 方法2:检查 headersSent if (!res.headersSent) { res.json(result) } } catch (err) { next(err) // 不要直接在此处 res.send() } // 危险:响应后异步操作 try { await sendNotification() // 应放在 try 块内 } catch (e) { logger.error(e) // 仅记录错误,不发送响应 } }) ``` #### 4. 添加全局错误监听(关键步骤) ```javascript // 捕获未处理 Promise 异常 process.on('unhandledRejection', (reason, promise) => { console.error('未处理拒绝:', promise, '原因:', reason) // 可在此处通知监控系统 }) // 捕获同步异常 process.on('uncaughtException', (err) => { console.error('未捕获异常:', err.stack) process.exit(1) // 建议重启进程 }) ``` --- ### 调试技巧 1. **检查响应头** 添加中间件检查响应状态: ```javascript app.use((req, res, next) => { const originalSend = res.send res.send = function (body) { console.log(`[响应] 状态: ${res.statusCode} 类型: ${res.get('Content-Type')}`) originalSend.call(this, body) } next() }) ``` 2. **模拟错误响应** 使用 Postman 测试不同状态码: ```javascript // 测试路由 app.get('/test-error', (req, res) => { res.status(req.query.code || 500).json({ error: { message: "测试错误" } }) }) ``` 3. **网络面板检查** 在浏览器开发者工具中: - 查看 **Network** 标签页的响应状态码 - 检查 **Response** 面板的原始数据格式 - 查看 **Console** 的完整错误堆栈 --- ### 最佳实践总结 1. **后端必须**: - 所有错误返回结构化 JSON 格式[^2] - 使用 `return res.send()` 终止执行流 - 注册错误处理中间件作为最后中间件 2. **前端必须**: - 检查 `response.ok` 或 `response.status` - 使用 `try/catch` 处理异步请求 - 显示用户友好的错误信息 3. **运维必须**: - 添加进程级错误监听 - 实现日志收集系统 - 设置应用性能监控(APM) > 遵循这些实践可彻底解决 `Uncaught (in promise)` 错误,并构建健壮的请求响应流程[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值