Pesquisa de site

javascript - Existe um operador RXJs melhor para usar neste cenário?


Eu tenho um método de serviço que faz o seguinte:

  1. Encontra o usuário no Banco de Dados pelo seu ID
  2. Verifica se um usuário foi encontrado
  3. Usa bcrypt para comparar a senha armazenada no banco de dados com uma senha fornecida como parâmetro
  4. Lança uma UnauthorizedException se a senha estiver incorreta, retorna o usuário se estiver correta.

Estou apenas tentando descobrir se existe uma maneira melhor de usar os operadores do RxJS para fazer isso porque não gosto de ter que usar pipe a partir do bcrypt.compare:

public validateUser(email: string, pass: string): Promise<UserDto> {
    return this.userService
      .findOne({ email })
      .pipe(
        map((user: UserDto) => {
          if (!user || !user.password) {
            return throwError(new UnauthorizedException());
          }
          return user;
        }),
        switchMap((user: UserDto) => {
          return from(
            bcrypt.compare(pass, user.password) as Promise<boolean>
          ).pipe(
            map((passwordIsCorrect) => ({
              passwordIsCorrect,
              user
            }))
          );
        }),
        switchMap((res) => {
          if (!res.passwordIsCorrect) {
            return throwError(new UnauthorizedException());
          }
          return of(res.user);
        })
      )
      .toPromise();
  }

Resposta #1

Eu não acho que haja um operador melhor para usar, mas você poderia simplificar o código para estar tudo dentro do mesmo switchMap assim:

  public validateUser(email: string, pass: string): Promise<UserDto> {
    return this.userService.findOne({ email }).pipe(
      switchMap(user => {
        if (!user?.password) {
          return throwError(new UnauthorizedException());
        }

        return from(bcrypt.compare(pass, user.password)).pipe(
          switchMap(passwordIsCorrect => passwordIsCorrect ? of(user) : throwError(new UnauthorizedException()))
        )
      })
    ).toPromise();
  }

No entanto, neste caso, parece que você está lutando para usar observáveis, (converter promessa em observável, apenas para converter de volta em promessa).

Mesmo que o userService retorne um observable, por que não convertê-lo diretamente em uma promise? Parece que o código seria muito mais simples:

  public async validateUser(email: string, pass: string): Promise<UserDto> {
    const user = await this.userService.findOne({ email }).toPromise();
    
    if (!user?.password || !await bcrypt.compare(pass, user.password)) {
      throw new UnauthorizedException();
    }
    
    return user;
  }

Resposta #2

Se você realmente não gosta do pipe interno, você poderia usar combineLatest para passar junto o valor de user assim como passwordIsCorrect:

public validateUser(email: string, pass: string): Promise<UserDto> {
    return this.userService
      .findOne({ email })
      .pipe(
        map((user: UserDto) => {
          if (!user || !user.password) {
            return throwError(new UnauthorizedException());
          }
          return user;
        }),
        switchMap((user: UserDto) => {
          return combineLatest([
            from(bcrypt.compare(pass, user.password)),
            of(user)
          ])
        }),
        map(([passwordIsCorrect, user]) => ({
          passwordIsCorrect,
          user
        })),
        switchMap((res) => {
          if (!res.passwordIsCorrect) {
            return throwError(new UnauthorizedException());
          }
          return of(res.user);
        })
      )
      .toPromise();
  }