Node.js中如何正确重抛http req.on('error')回调内的错误?
我正在AKS集群中配置一个Kubernetes Job,启动后执行uploadRepairCatalog() JavaScript函数,通过端点上传维修目录到服务器。
核心流程逻辑
uploadRepairCatalog()的try块:先调用createFirebaseUser()生成服务器所需的authToken,再调用sendRepairData()循环发起HTTP请求,两个函数均可能抛出错误catch块:若上述函数报错,调用deleteRepairs()清理sendRepairData()已存入数据库的数据,随后重抛错误触发容器重启finally块:调用deleteFirebaseUser()清理初始创建的Firebase用户
遇到的问题
本地测试时,sendRepairData()因无法连接服务器报错,但此时deleteFirebaseUser()并未执行;重新启动代码后,createFirebaseUser()因用户已存在报错,finally块却能正常执行。
调试后确认:sendRepairData()中req.on('error', (error) => {})回调内抛出的错误,无法被sendRepairData()的catch块捕获,因此无法传递到uploadRepairCatalog()的catch块;但直接抛出普通Error时,流程能正常执行。
相关代码
async function sendRepairData() { // Read the catalog JSON file try { const catalogData = fs.readFileSync('catalog.json'); const catalog = JSON.parse(catalogData); // Access the repairs array from the catalog repairs = catalog.repairs; console.log('expected repairs to send: ', repairs.length); for (const repair of repairs) { const options = { host: host, port: port, path: path, method: 'POST', headers: { 'Content-Type': 'application/json', AuthToken: authToken, apikey: apiKey, }, }; const req = http.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { console.log('Response:', data); // save response data for resetting the db in case of error savedRepairs.push(JSON.parse(data)); }); }); req.on('error', (error) => { console.log('sendRepairData() http request error:', error); throw error; // 此处错误无法被外层catch捕获 // throw Error(`${error.name}, ${error.message}, ${error.stack}`); }); req.write(JSON.stringify(repair)); req.end(); // throw Error('test error from sendRepairData()'); // 直接抛出错误能被捕获 } } catch (error) { console.error('\n\nsendRepairData() catch\n\nerror :', error); throw error; } }
核心原因
Node.js的事件回调(如req.on('error'))属于异步任务,抛出的错误不会冒泡到当前的try/catch块——因为try/catch仅能捕获同步代码或通过await等待的Promise异步代码中的错误。回调函数的执行上下文脱离了原try块的作用域,所以直接throw的错误无法被捕获。
解决方法:将HTTP请求封装为Promise
把http.request的回调逻辑包装成Promise,通过resolve和reject管理异步状态,再用await等待每个请求完成,这样错误就能被外层的try/catch捕获。
修改后的代码
async function sendRepairData() { // Read the catalog JSON file try { const catalogData = fs.readFileSync('catalog.json'); const catalog = JSON.parse(catalogData); // Access the repairs array from the catalog repairs = catalog.repairs; console.log('expected repairs to send: ', repairs.length); // 用await确保每个请求完成后再处理下一个 for (const repair of repairs) { await new Promise((resolve, reject) => { const options = { host: host, port: port, path: path, method: 'POST', headers: { 'Content-Type': 'application/json', AuthToken: authToken, apikey: apiKey, }, }; const req = http.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { console.log('Response:', data); savedRepairs.push(JSON.parse(data)); resolve(); // 请求成功,标记Promise完成 }); }); req.on('error', (error) => { console.log('sendRepairData() http request error:', error); reject(error); // 将错误传递给Promise的reject }); req.write(JSON.stringify(repair)); req.end(); }); } } catch (error) { console.error('\n\nsendRepairData() catch\n\nerror :', error); throw error; } }
关键修改说明
- Promise封装:用
new Promise()包裹每个http.request的逻辑,请求成功时调用resolve(),错误时调用reject(error),将异步操作转为Promise链 - await等待:在for循环中用
await等待每个Promise完成,确保异步错误能被外层try/catch捕获 - 错误传递:回调中不再直接throw错误,而是通过
reject(error)将错误传递到Promise链,最终被sendRepairData()的catch块捕获并重抛
修改后,req.on('error')中的错误会正常触发sendRepairData()的catch块,进而传递到uploadRepairCatalog()的catch块执行清理逻辑,finally块也会正常执行deleteFirebaseUser()。
内容的提问来源于stack exchange,提问作者Vincenzo

