Node.js Async_hooks使用await后请求上下文丢失问题求助
使用async_hooks实现请求上下文管理:await Promise后上下文丢失问题
问题描述
使用async_hooks实现Node.js请求上下文管理时,在await Promise后调用hook.getRequestContext()返回undefined。经排查,await Promise会生成新的executionAsyncId,而该ID对应的上下文未被正确继承。
相关代码
路由处理函数
var hook = require('../../services/hooks.service'); const test11 = async (req,res)=>{ let myPromise = new Promise(function(myResolve, myReject) { let x = 0; if (x == 0) { myResolve("OK"); } else { myReject("Error"); } }); await myPromise; console.log("xxx",hook.getRequestContext()) // 返回undefined }
hook.service.js(原代码)
const asyncHooks = require('async_hooks'); const { v4 } = require('uuid'); const store = new Map(); const asyncHook = asyncHooks.createHook({ init: (asyncId, _, triggerAsyncId) => { if (store.has(triggerAsyncId)) { store.set(asyncId, store.get(triggerAsyncId)) } }, destroy: (asyncId) => { if (store.has(asyncId)) { store.delete(asyncId); } } }); asyncHook.enable(); const createRequestContext = (data, requestId = v4()) => { const requestInfo = { requestId, data }; console.log(asyncHooks.executionAsyncId()) store.set(asyncHooks.executionAsyncId(), requestInfo); return requestInfo; }; const getRequestContext = () => { console.log(asyncHooks.executionAsyncId()) return store.get(asyncHooks.executionAsyncId()); }; module.exports = { createRequestContext, getRequestContext };
app.js
const hook = require('./src/services/hooks.service'); app.use((request, response, next) => { const data = { headers: request.headers }; hook.createRequestContext(data); next(); });
解决方案
问题核心是:await后的新执行上下文的executionAsyncId对应的父级ID(Promise的asyncId)可能已被destroy钩子清理,导致无法直接获取上下文。我们可以通过跟踪asyncId的父级关联链,在获取上下文时向上遍历,直到找到有效的上下文条目。
修改后的hook.service.js代码:
const asyncHooks = require('async_hooks'); const { v4 } = require('uuid'); const store = new Map(); // 存储asyncId -> 请求上下文 const triggerMap = new Map(); // 存储asyncId -> 父级triggerAsyncId const asyncHook = asyncHooks.createHook({ init: (asyncId, type, triggerAsyncId) => { // 记录当前asyncId的父级ID triggerMap.set(asyncId, triggerAsyncId); // 继承父级的上下文 if (store.has(triggerAsyncId)) { store.set(asyncId, store.get(triggerAsyncId)); } }, destroy: (asyncId) => { // 清理当前asyncId的存储条目 if (store.has(asyncId)) { store.delete(asyncId); } triggerMap.delete(asyncId); } }); asyncHook.enable(); const createRequestContext = (data, requestId = v4()) => { const requestInfo = { requestId, data }; const currentId = asyncHooks.executionAsyncId(); store.set(currentId, requestInfo); return requestInfo; }; const getRequestContext = () => { let currentId = asyncHooks.executionAsyncId(); // 向上遍历asyncId链,直到找到上下文或到达根节点(asyncId=0) while (currentId !== 0) { if (store.has(currentId)) { return store.get(currentId); } currentId = triggerMap.get(currentId); } return undefined; }; module.exports = { createRequestContext, getRequestContext };
原理说明
- 跟踪父级关联:在
init钩子中记录每个asyncId对应的父级triggerAsyncId,建立关联链; - 遍历获取上下文:在
getRequestContext中,从当前executionAsyncId开始,向上遍历父级链,直到找到存在的上下文条目; - 清理资源:在
destroy钩子中同时清理store和triggerMap的条目,避免内存泄漏。
这样即使await后的新asyncId没有直接存储上下文,也能通过父级链找到最初的请求上下文。
内容的提问来源于stack exchange,提问作者Muhammad Nabeel
相关产品推荐
相关产品推荐

