logologo
文档仓库
文档仓库
logologo
开始

概述

第一步
控制器
提供者
模块
中间件
异常过滤器
管道
守卫
拦截器
自定义装饰器

基础

自定义提供程序
异步提供者
动态模块
注入作用域
循环依赖
模块引用
懒加载模块
执行上下文
生命周期事件
发现服务
平台无关
单元测试

技术

配置
SQL
Mongo
验证
缓存
序列化
版本控制
任务调度
队列
日志
Cookies
事件
压缩
文件上传
文件流
HTTP 模块
Session
MVC
性能(Fastify)
SSE

安全

认证
授权
加密与哈希
Helmet
CORS
CSRF
速率限制

GraphQL

快速开始
解析器
变更
订阅
标量
指令
接口
联合与枚举
字段中间件
类型映射
插件
复杂度
扩展
CLI 插件
生成SDL
共享模型
其他功能
联邦

WebSocket

网关
异常过滤器
管道
守卫
拦截器
适配器

微服务

基础
Redis
MQTT
NATS
RabbitMQ
Kafka
gRPC
自定义传输
异常过滤器
管道
守卫
拦截器
部署
独立应用程序

CLI

概述
工作区
库
用法
脚本

OpenAPI

介绍
装饰器
类型映射
操作
其他特性
安全
类型与参数
CLI 插件

实用示例

REPL
CRUD生成器
SWC
Passport(认证)
热重载
MikroORM
TypeORM
Mongoose
Sequelize
路由模块
Swagger
健康检查
CQRS
Compodoc
Prisma
Sentry
静态资源
Commander
异步本地存储
Necord
套件(原Automock)

常见问题

Serverless
HTTP 适配器
长连接
全局前缀
原始请求体
混合应用
HTTPS & 多服务器
请求生命周期
错误

开发工具

概述
CI/CD
迁移指南
API参考(官方)

生态与案例

谁在用
精彩资源

支持

支持

社区

贡献者

最后更新于: 2025/11/18 02:11:37

上一页Commander
下一页Necord

#异步本地存储 (Async Local Storage)

AsyncLocalStorage 是一个 Node.js API(基于 async_hooks API),它提供了一种无需显式传递函数参数就能在应用中传播本地状态的替代方案。这类似于其他语言中的线程本地存储。

异步本地存储的核心思想是我们可以用 AsyncLocalStorage#run 调用包装某些函数调用。所有在被包装调用内执行的代码都能访问相同的 store,且每个调用链都将拥有唯一的存储空间。

在 NestJS 上下文中,这意味着如果我们能在请求生命周期中找到某个位置来包装请求的剩余代码,就能访问和修改仅对该请求可见的状态,这可以作为 REQUEST 作用域提供程序的替代方案,并解决其部分局限性。

或者,我们可以使用 ALS(异步本地存储)仅为系统的一部分(例如事务对象)传播上下文,而无需在服务间显式传递,这样可以提高隔离性和封装性。

#自定义实现

NestJS 本身并未为 AsyncLocalStorage 提供任何内置抽象,因此让我们通过最简单的 HTTP 案例来了解如何自行实现,以便更好地理解整个概念:

提示

如需使用现成的专用包,请继续阅读下文。

  1. 首先,在某个共享源文件中创建一个新的 AsyncLocalStorage 实例。由于我们使用 NestJS,让我们也将其转换为带有自定义提供者的模块。
als.module.ts
@Module({
 providers: [
   {
     provide: AsyncLocalStorage,
     useValue: new AsyncLocalStorage(),
   },
 ],
 exports: [AsyncLocalStorage],
})
export class AlsModule {}
提示

AsyncLocalStorage 是从 async_hooks 导入的。

  1. 我们只关注 HTTP,所以让我们使用中间件将 next 函数用 AsyncLocalStorage#run 包装起来。由于中间件是请求最先到达的地方,这将使得 store 在所有增强器和系统其余部分中都可用。
app.module.ts
@Module({
 imports: [AlsModule],
 providers: [CatsService],
 controllers: [CatsController],
})
export class AppModule implements NestModule {
 constructor(
   // inject the AsyncLocalStorage in the module constructor,
   private readonly als: AsyncLocalStorage
 ) {}

 configure(consumer: MiddlewareConsumer) {
   // bind the middleware,
   consumer
     .apply((req, res, next) => {
       // populate the store with some default values
       // based on the request,
       const store = {
         userId: req.headers['x-user-id'],
       };
       // and pass the "next" function as callback
       // to the "als.run" method together with the store.
       this.als.run(store, () => next());
     })
     .forRoutes('*path');
 }
}
  1. 现在,在请求生命周期的任何地方,我们都可以访问本地存储实例。
cats.service
@Injectable()
export class CatsService {
  constructor(
    // We can inject the provided ALS instance.
    private readonly als: AsyncLocalStorage,
    private readonly catsRepository: CatsRepository,
  ) {}

  getCatForUser() {
    // The "getStore" method will always return the
    // store instance associated with the given request.
    const userId = this.als.getStore()["userId"] as number;
    return this.catsRepository.getForUser(userId);
  }
}
  1. 就这样,我们现在有了无需注入整个 REQUEST 对象就能共享请求相关状态的方法。
警告

请注意,虽然该技术在许多用例中很有用,但它本质上会使代码流程变得晦涩(创建隐式上下文),因此请负责任地使用它,尤其要避免创建上下文式的" 上帝对象 "。

#NestJS CLS

nestjs-cls 包相比直接使用原生 AsyncLocalStorage(CLS 是 continuation-local storage 的缩写)提供了多项开发者体验改进。它将实现抽象为一个 ClsModule,为不同传输方式(不仅限于 HTTP)提供多种初始化 store 的方法,同时还支持强类型。

然后可以通过可注入的 ClsService 访问存储,或者通过使用代理提供者将其完全从业务逻辑中抽象出来。

nestjs-cls

nestjs-cls 是第三方包,不由 NestJS 核心团队维护。如发现该库的任何问题,请在相应仓库中报告。

#安装

除了对 @nestjs 库的对等依赖外,它仅使用 Node.js 内置 API。可像安装其他包一样安装它。

npm i nestjs-cls

#使用方法

可以使用 nestjs-cls 实现与上文描述的类似功能,如下所示:

  1. 在根模块中导入 ClsModule。
app.module
@Module({
  imports: [
    // Register the ClsModule,
    ClsModule.forRoot({
      middleware: {
        // automatically mount the
        // ClsMiddleware for all routes
        mount: true,
        // and use the setup method to
        // provide default store values.
        setup: (cls, req) => {
          cls.set('userId', req.headers['x-user-id']);
        },
      },
    }),
  ],
  providers: [CatsService],
  controllers: [CatsController],
})
export class AppModule {}
  1. 然后就可以使用 ClsService 来访问存储值。
cats.service
@Injectable()
export class CatsService {
  constructor(
    // We can inject the provided ClsService instance,
    private readonly cls: ClsService,
    private readonly catsRepository: CatsRepository,
  ) {}

  getCatForUser() {
    // and use the "get" method to retrieve any stored value.
    const userId = this.cls.get('userId');
    return this.catsRepository.getForUser(userId);
  }
}
  1. 为了获得由 ClsService 管理的存储值的强类型(同时获取字符串键的自动建议),我们可以在注入时使用可选类型参数 ClsService<MyClsStore>。
export interface MyClsStore extends ClsStore {
  userId: number;
}
提示

也可以让包自动生成一个请求 ID,稍后通过 cls.getId() 访问它,或者使用 cls.get(CLS_REQ) 获取整个请求对象。

#测试

由于 ClsService 只是另一个可注入的提供者,因此在单元测试中可以完全模拟它。

然而,在某些集成测试中,我们可能仍希望使用真实的 ClsService 实现。在这种情况下,我们需要用 ClsService#run 或 ClsService#runWith 调用来包装上下文感知的代码片段。

describe('CatsService', () => {
  let service: CatsService
  let cls: ClsService
  const mockCatsRepository = createMock<CatsRepository>()

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      // 设置 up most of the testing module as we normally would.
      providers: [
        CatsService,
        {
          provide: CatsRepository
          useValue: mockCatsRepository
        }
      ],
      imports: [
        // 导入 the static version of ClsModule which only provides
        // the ClsService, but does not set up the store in any way.
        ClsModule
      ],
    }).compile()

    service = module.get(CatsService)

    // Also retrieve the ClsService for later use.
    cls = module.get(ClsService)
  })

  describe('getCatForUser', () => {
    it('retrieves cat based on user id', async () => {
      const expectedUserId = 42
      mocksCatsRepository.getForUser.mockImplementationOnce(
        (id) => ({ userId: id })
      )

      // Wrap the test call in the `runWith` method
      // in which we can pass hand-crafted store values.
      const cat = await cls.runWith(
        { userId: expectedUserId },
        () => service.getCatForUser()
      )

      expect(cat.userId).toEqual(expectedUserId)
    })
  })
})

#更多信息

访问 NestJS CLS GitHub 页面获取完整的 API 文档和更多代码示例。