我在 NodeJS +16 中使用 NestJS 作为后端框架
我的想法是拥有一个 @Injectable() 服务,除其他外,该服务将具有以下方法:
hasUserSomeStuff(){
const user = UserStorage.get()
if(user) {
// do magic
}
然后传递此服务,就像通常在 NestJS 中完成的那样
为了避免将请求传递到兔子洞,或冒泡请求范围,以便为每个请求实例化每个依赖项,但也要避免在需要从当前请求获取用户并执行操作的任何地方使用 UserStorage
我已经多次阅读文档,据我了解,节点将负责为每个异步上下文(在我的情况下为每个请求)实例化一个新存储,但在我身上发生的事情似乎是当我第一次运行时我的后端,它工作得很好,我从当前请求中获取了用户,但是一旦第一个异步上下文/承诺完成,我就检索了消费者的数据,并在下一个请求中 UserStorage 返回一个未定义的(如文档所述如果您位于同一异步上下文之外,则会出现这种情况,我想这不是发生的情况,因为它应该是一个全新的异步上下文)
但是,如果我调试,似乎会发生的是,在应用程序准备好使用之前,调用此 UserStorage 并在 init 处实例化一个新的 AsyncLocalStorage,然后第一个请求返回未定义的用户。
我不明白发生了什么事,任何人都可以帮助我,或者有更好的方法来实现我的目标吗?
提前致谢
首先我们需要了解 AsyncLocalStorage 的工作原理。 您将能够检索从 AsyncLocalStorage 实例显式执行的上下文中的任何信息。
例如:
//global scope
const context = new AsyncLocalStorage()
//with typescript
//const context = new AsyncLocalStorage<UserPayload>()
async function test() {
const userPayload = {
id: 1,
name: 'hiki9'
}
context.run(userPayload, () => whateverFunction())
}
async function whateverFunction() {
const unknownPayload = context.getStore();
//which is userPayload
}
在
whateverFunction
之后调用的每个函数都将共享上下文。
假设您使用的是 NestJS,以下是常见用法。
//author-context.service.ts
export interface AuthorContext {
id: number
name: string
}
@Injectable()
export class AuthorUserContextService extends AsyncLocalStorage<AuthorContext>{}
//user.service.ts
@Injectable()
export class UserCreateService {
constructor(private readonly context: AuthorUserContextService){}
execute(dto: UserCreateDTO) {
const store: AuthorContext = this.context.getStore()
//const user = new User(store.whatever)
//await this.userRepository.save(user)
//inside userRepository, you can still `getStore`
}
}
然后您需要在执行线程开始时
AsyncLocalStorage.run
。如果您正在使用 http 执行一些 api,只需将其绑定在控制器的开头即可。在这种情况下,使用 NestJS 会更好地配置一个 MiddlewareConsumer
。
//iam.module.ts
import { MiddlewareConsumer, Module } from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
@Module({
providers: [
AuthorUserContextService,
UserCreateService,
]
})
export class IamModule{
constructor(
//whateverServiceThatYouNeed: WhateverServiceThatYouNeed
) {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req: Request, res: Response, next: NextFunction) => {
const userId = request.headers['user-internal-id'] //it will depends
const store = {
userId,
requestBody: request.body,
//...
}
this.context.run(store, () => next());
})
.forRoutes('*');
}
}