You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 };

原理说明

  1. 跟踪父级关联:在init钩子中记录每个asyncId对应的父级triggerAsyncId,建立关联链;
  2. 遍历获取上下文:在getRequestContext中,从当前executionAsyncId开始,向上遍历父级链,直到找到存在的上下文条目;
  3. 清理资源:在destroy钩子中同时清理store和triggerMap的条目,避免内存泄漏。

这样即使await后的新asyncId没有直接存储上下文,也能通过父级链找到最初的请求上下文。

内容的提问来源于stack exchange,提问作者Muhammad Nabeel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 17:56:09