为何Axios请求错误在try/catch+await下仍出现未处理异常?
问题描述
我用TypeScript开发一款homebridge插件,使用Axios处理网络请求。插件会先登录本地网络中的服务器,之后按固定间隔轮询获取服务器状态信息;当会话过期时,服务器返回302错误并重定向到登录页,因此插件禁用了重定向,监测30x错误以触发重新登录、更新会话后重试请求。所有对网络请求函数的调用都包裹在try { await... } catch { }块中,但仍有部分错误绕过错误处理机制,传播到主事件循环导致插件崩溃。
相关代码
类构造函数
// [...] this.client = axios.create({ baseURL: this.httpPrefix + this.IPAddress, timeout: 10000, maxRedirects: 0, method: "post", headers: { 'content-type': 'application/x-www-form-urlencoded' } }); // [...]
轮询函数
[...] try { const response = await this.makeRequest('user/seq.xml', {'sess': this.sessionID}); // [...] } catch (error) { throw(error); }
请求处理辅助函数
private async makeRequest(address: string, payload = {}) { try { return await this.client({ url: address, data: payload }); } catch (error) { if (axios.isAxiosError(error)) { const response = error.response; if (response == undefined) throw error; if (response.status >= 300 && response.status < 400) { await this.login(); payload['sess'] = this.sessionID; const response = await this.client({ url: address, data: payload }); return response; } else throw(error); } else throw (error); } }
轮询调度函数
async updateAccessories() { // [...] try { await this.securitySystem.poll(); // [...] } catch (error) { this.log.error((<Error>error).message); await delay(retryDelayDuration); } setTimeout(this.updateAccessories.bind(this), this.pollTimer); }
delay辅助函数
export function delay(milliseconds: number) { return new Promise(resolve => setTimeout(resolve, milliseconds)); }
错误现象
间歇性出现未处理错误,导致插件崩溃:
AxiosError: Request failed with status code 302 at settle (/usr/lib/node_modules/homebridge-caddx-interlogix/node_modules/axios/lib/core/settle.js:19:12) at IncomingMessage.handleStreamEnd (/usr/lib/node_modules/homebridge-caddx-interlogix/node_modules/axios/lib/adapters/http.js:495:11) at IncomingMessage.emit (node:events:525:35) at endReadableNT (node:internal/streams/readable:1358:12) at processTicksAndRejections (node:internal/process/task_queues:83:21)
解决思路
1. 修复makeRequest的重试错误处理
当前makeRequest在捕获30x错误后,重新调用this.client时未包裹错误处理,若重试请求再次失败(如登录后会话仍无效、网络波动),错误会直接逃逸。修改代码覆盖重试分支的错误捕获:
private async makeRequest(address: string, payload = {}) { try { return await this.client({ url: address, data: payload }); } catch (error) { if (axios.isAxiosError(error)) { const response = error.response; if (!response) { throw error; } if (response.status >= 300 && response.status < 400) { await this.login(); payload['sess'] = this.sessionID; // 重试请求添加错误捕获 try { return await this.client({ url: address, data: payload }); } catch (retryError) { throw retryError; } } else { throw error; } } else { throw error; } } }
2. 移除轮询函数中不必要的错误重抛
轮询函数里捕获错误后直接throw(error),会让错误继续向上传播,增加逃逸风险。改为直接在内部处理错误:
// 修改轮询函数 try { const response = await this.makeRequest('user/seq.xml', {'sess': this.sessionID}); // [...] } catch (error) { this.log.error('轮询请求失败:', (error as Error).message); // 此处不再重抛错误,由上层updateAccessories统一处理 }
3. 确保updateAccessories的异步错误完全被捕获
用finally块确保无论请求成功或失败,都会触发下一次轮询,同时避免异步回调的错误逃逸:
async updateAccessories() { try { await this.securitySystem.poll(); // [...] } catch (error) { this.log.error((<Error>error).message); await delay(retryDelayDuration); } finally { // 确保无论结果如何,都会启动下一次轮询 setTimeout(() => this.updateAccessories(), this.pollTimer); } }
4. 添加全局错误兜底监听
在插件初始化时添加全局未捕获错误监听,防止极端情况下的错误导致插件崩溃:
// 插件构造函数中 process.on('unhandledRejection', (reason) => { this.log.error('未处理的Promise拒绝:', reason); // 可在此添加会话重置等恢复逻辑 }); process.on('uncaughtException', (error) => { this.log.error('未捕获的异常:', error); });
内容的提问来源于stack exchange,提问作者Flareman
相关产品推荐
相关产品推荐

