NestJS/RxJS-是否有更简单的方法;遵守「;一次?

NestJS/RxJS-是否有更简单的方法;遵守「;一次?,rxjs,nestjs,Rxjs,Nestjs,我对NestJS和RxJS都是新手。我正在努力用一个惯用的RxJS编写东西,因为这个项目的目的之一是更好地学习它们,而不是试图绕过或绕过它们的API 总之,我有一个逻辑,我从OAuth2服务器上查找JWKSet。我使用NestJS HttpService来实现这一点,它返回一个可观察的。然后,我使用该可观察对象将结果设置为ReplaySubject。然后,在我的JwtStrategy中,我使用secretroKeyProvider函数订阅ReplaySubject,以便在每次收到HTTP请求时获

我对NestJS和RxJS都是新手。我正在努力用一个惯用的RxJS编写东西,因为这个项目的目的之一是更好地学习它们,而不是试图绕过或绕过它们的API

总之,我有一个逻辑,我从OAuth2服务器上查找JWKSet。我使用NestJS HttpService来实现这一点,它返回一个可观察的。然后,我使用该可观察对象将结果设置为ReplaySubject。然后,在我的JwtStrategy中,我使用secretroKeyProvider函数订阅ReplaySubject,以便在每次收到HTTP请求时获取值

现在,我确信这种方法有很多错误,因为我几乎不知道如何使用RxJS。我最大的问题是最后一部分。当我在secretroKeyProvider函数中订阅ReplaySubject时,我立即取消订阅。这是因为我想清理剩余订阅

总的来说,我觉得这是非常错误的,订阅和立即取消订阅。我觉得我做错了什么。我正在寻求对该代码的审查,以了解我可以做哪些改进

虽然下面的所有代码都有效,但我的目标是引导我更好、更正确地使用RxJS

@Injectable()
export class JwkService implements OnModuleInit, OnModuleDestroy {
    private readonly logger = new Logger(JwkService.name);

    readonly key = new ReplaySubject<string>();
    private subscription: Subscription;

    constructor(
        private httpService: HttpService,
        private configService: ConfigService
    ) {}

    onModuleInit(): void {
        this.subscription = this.httpService
            .get(`${this.configService.get<string>(AUTH_SERVER_HOST)}${jwkUri}`)
            .pipe(
                map((res: AxiosResponse<JwkSet>) => jwkToPem(res.data.keys[0]))
            )
            .subscribe({
                next: (key) => this.key.next(key),
                error: (error: Error) => {
                    this.logger.error(
                        'CRITICAL ERROR: Unable to load JWKSet',
                        ajaxErrorHandler(error)
                    );
                }
            });
    }

    onModuleDestroy(): void {
        if (this.subscription) {
            this.subscription.unsubscribe();
        }
    }
}

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
    private readonly logger = new Logger(JwtStrategy.name);

    constructor(
        private readonly jwkService: JwkService
    ) {
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: false,
            secretOrKeyProvider: (
                req: Request,
                rawJwt: string,
                done: doneFn
            ) => {
                jwkService.key
                    .subscribe({
                        next: (value: string) => done(null, value),
                        error: (error: Error) => {
                            this.logger.error(
                                'Error getting JWK key',
                                ajaxErrorHandler(error)
                            );
                            done(error);
                        }
                    })
                    .unsubscribe();
            }
        });
    }
}
@Injectable()
导出类JwkService实现OnModuleInit、OnModuleDestroy{
私有只读记录器=新记录器(JwkService.name);
只读键=新的ReplaySubject();
私人认购:认购;
建造师(
私有httpService:httpService,
专用配置服务:配置服务
) {}
onModuleInit():void{
this.subscription=this.httpService
.get(`${this.configService.get(AUTH\u SERVER\u HOST)}${jwkUri}`)
.烟斗(
map((res:AxiosResponse)=>jwkToPem(res.data.keys[0]))
)
.订阅({
下一个:(键)=>这个。键。下一个(键),
错误:(错误:错误)=>{
这是一个错误(
'严重错误:无法加载JWKSet',
ajaxErrorHandler(错误)
);
}
});
}
onModuleDestroy():void{
如果(此订阅){
this.subscription.unsubscripte();
}
}
}
@可注射()
导出类JwtStrategy扩展了PassportStrategy(策略){
私有只读记录器=新记录器(JwtStrategy.name);
建造师(
专用只读jwkService:jwkService
) {
超级({
jwtFromRequest:ExtractJwt.FromAuthHeaderAsberToken(),
忽略过期日期:false,
分泌密钥提供者:(
请求:,
rawJwt:string,
完成:doneFn
) => {
jwkService.key
.订阅({
下一步:(value:string)=>done(null,value),
错误:(错误:错误)=>{
这是一个错误(
'获取JWK密钥时出错',
ajaxErrorHandler(错误)
);
完成(错误);
}
})
.取消订阅();
}
});
}
}

如果您不需要
取消订阅
,RxJS会为您提供一些管道:

     .get(`${this.configService.get<string>(AUTH_SERVER_HOST)}${jwkUri}`)
        .pipe(
            take(1),
            map((res: AxiosResponse<JwkSet>) => jwkToPem(res.data.keys[0]))
        )
     ...
.get(`this.configService.get(AUTH\u SERVER\u HOST)}${jwkUri}`)
.烟斗(
以(1)为例,
map((res:AxiosResponse)=>jwkToPem(res.data.keys[0]))
)
...

.get(`this.configService.get(AUTH\u SERVER\u HOST)}${jwkUri}`)
.烟斗(
第一个(),
map((res:AxiosResponse)=>jwkToPem(res.data.keys[0]))
)
...

first()。如果在任何情况下都没有价值,那么您可以手动取消订阅,或只是完成主题。

这很好。还有一个问题,关于错误处理呢?管道似乎无法做到这一点,除非我遗漏了什么。您可以使用catchError管道,然后使用throwError(e=>newerror(e))作为一个简单的解决方案。并在订阅中解析它:subscribe({下一步:()=>{..通常的订阅fn},错误:()=>{..在throwError上执行,},完成:()=>{最后做点什么})
     .get(`${this.configService.get<string>(AUTH_SERVER_HOST)}${jwkUri}`)
        .pipe(
            first(),
            map((res: AxiosResponse<JwkSet>) => jwkToPem(res.data.keys[0]))
        )
     ...