Node.js/Express如何创建仅在请求生命周期内有效的全局变量
Express 请求级作用域存储实现方案
Node.js 和 Express 没有直接提供开箱即用的请求生命周期全局变量能力,但通过原生API和成熟封装完全可以实现简洁、无跨请求污染、自动清理的效果,不需要靠闭包硬写。
原生无依赖实现(推荐优先使用)
Node.js 13.10 及以上版本内置了 AsyncLocalStorage 异步上下文追踪能力,配合Express的响应生命周期事件,可以零依赖实现需求:
- 每个请求进入时创建专属的存储对象,绑定到当前请求的异步调用链上
- 监听响应结束、连接断开两个事件,请求终止时自动清空存储、解除引用,避免内存泄漏
- 业务代码任意位置都可以直接访问当前请求的存储,不需要层层透传req对象
示例代码:
const express = require('express'); const { AsyncLocalStorage } = require('async_hooks'); const app = express(); // 初始化异步存储实例 const requestContext = new AsyncLocalStorage(); // 注册全局中间件,每个请求进入时初始化作用域 app.use((req, res, next) => { // 初始化当前请求专属的存储容器 const scopedStore = new Map(); // 定义清理逻辑 const cleanUp = () => { scopedStore.clear(); res.removeListener('finish', cleanUp); res.removeListener('close', cleanUp); }; // 正常响应结束时清理 res.on('finish', cleanUp); // 客户端中途断连时兜底清理 res.on('close', cleanUp); // 将存储绑定到当前请求的整个异步链路,执行后续逻辑 requestContext.run(scopedStore, next); }); // 封装通用的存取方法,业务代码直接调用即可,不需要传req function setContextValue(key, value) { const store = requestContext.getStore(); if (store) store.set(key, value); } function getContextValue(key) { const store = requestContext.getStore(); return store ? store.get(key) : undefined; }
使用时不需要考虑请求传递问题,比如在路由、控制器、甚至独立的service工具函数里,都可以直接调用setContextValue/getContextValue操作当前请求的专属数据,不会和其他请求串值。
如果你的场景不需要跨多层函数访问数据,最简单的方案是直接把存储挂在req对象上,同样配合finish/close事件做清理即可,只是需要把req对象透传到用到数据的函数里,灵活度稍差。
成熟开源封装
如果不想自己维护封装逻辑,可以直接用社区经过大量生产验证的包,底层都是基于上述AsyncLocalStorage实现:
express-http-context:专门适配Express的请求上下文包,API极简,内置自动清理逻辑,和Express中间件生态完全兼容- 低版本Node.js(低于13.10)可以使用
cls-hooked实现类似能力,不过高版本Node.js更推荐优先用原生AsyncLocalStorage方案,稳定性更好
避坑说明
- 不要把请求专属数据直接挂载到global全局对象上:Node.js单线程事件循环模型下,global对象的属性会被所有并发请求共享,必然出现跨请求数据污染
- 不要只监听
finish事件做清理:遇到客户端主动断连、请求超时断开的场景,finish事件不会触发,必须同时监听close事件做兜底,否则会出现内存泄漏 - 请求作用域存储不要存放过大的对象,避免不必要的内存占用,不需要的数据可以主动删除,不用等请求结束自动清理
内容的提问来源于stack exchange,提问作者Raul
相关产品推荐
相关产品推荐

