AuthorizeService isAuthenticated () subscrever sendo chamado várias vezes com rxjs concat

Aug 31 2020

Estou usando um serviço de autenticação do lado do cliente padrão de um modelo do Visual Studio.

Há um AuthorizeService de texto digitado que tem uma função chamada isAuthenticated que chama a função abaixo e verifica se ela é nula ou não.

A função getUser:

public getUser(): Observable<IUser> {
    return concat(
      this.userSubject.pipe(take(1), filter(u => !!u)),
      this.getUserFromStorage().pipe(filter(u => !!u), tap(u => this.userSubject.next(u))),
      this.userSubject.asObservable());
  }

quando .subscribe é chamado na função acima. Inscrever-se é chamado três vezes. Presumivelmente, para cada observável na função concat. Usando getUser acima, espero que o subscribe seja chamado uma vez. Como eu faria isso?

Tentei converter o acima em promessas aninhadas que retornam um valor, mas sem sucesso por algum motivo, depois que o resultado é retornado, ele retorna resolve (nulo) mesmo que o usuário exista no armazenamento da sessão

Respostas

1 MrkSef Aug 31 2020 at 23:29

Estou assumindo que você deseja que getUser () retorne apenas um usuário. No momento, sua lógica diz "Obtenha o valor atual de userSubject e obtenha um usuário do armazenamento e também obtenha os valores atuais e futuros de userSubject."

Isso significa que se o valor de userSubject for verdadeiro, você terá pelo menos 3 usuários de volta. Isso é o que sua lógica está dizendo.

Não tenho certeza do que você quer dizer com 'inscrever-se apenas uma vez', mas presumo que você queira dizer 'retornar apenas 1 usuário'. Uma maneira simples de fazer isso é pegar apenas um valor de sua chamada concat:

return concat(...).pipe(take(1));

Isso pode levar a um comportamento imprevisível. Qualquer um dos três fluxos que emita um valor primeiro será o valor que você tomar. Se getUserFromStorage () levar algum tempo para ser concluído, você sempre receberá um valor nulo de volta. Estou supondo que isso é o que acontece com você quando está aninhando promessas (embora eu precise ver seu código para ter certeza).

A melhor maneira de fazer isso é com switchMap ou mergeMap (ambos funcionarão neste caso). Também estou supondo que você só deseja obter um usuário do back-end se não houver um no userSubject. Essa abordagem estaria efetivamente armazenando em cache o usuário autenticado no momento.

public getUser(): Observable<IUser> {
  return this.userSubject.pipe(
    take(1),
    mergeMap(u => {
      if(u) return of(u);
      return this.getUserFromStorage().pipe(
        tap(u => u && this.userSubject.next(u))
      );
    }), 
    take(1)
  );
}

O que isso faz? Ele tenta obter um usuário do armazenamento apenas se o usuário do userSubject não for verdadeiro. this.getUserFromStorage () nunca é chamado (ou inscrito) se houver um usuário no userSubject. É importante notar que a segunda chamada para take (1) não é necessária se getUserFromStorage () retornar apenas um valor. Isso também pressupõe que getUserFromStorage () retorna nulo se não houver nenhum usuário no armazenamento.

Finalmente, removi todos os filtros, pois parece (de sua descrição) que você deseja que este fluxo retorne um valor nulo se não houver nenhum usuário no Assunto e nenhum usuário no armazenamento. Se filtrarmos um retorno nulo, nunca retornaremos nulo. Em vez disso, o que fiz é retornar nulo apenas se getUserFromStorage () retornar nulo.

RafiHenig Aug 31 2020 at 21:35

Considere usar em forkJoinvez disso (quando todos os observáveis ​​forem concluídos, emita o último valor emitido de cada um como um Array), conforme demonstrado abaixo:

Observação: adicionei o takeoperador ao último observável para que seja concluído

public getUser(): Observable <[IUser, IUser, IUser]> {
  return forkJoin(
    this.userSubject.pipe(take(1), filter(u => !!u)),
    this.getUserFromStorage().pipe(filter(u => !!u), tap(u => this.userSubject.next(u))),
    this.userSubject.asObservable().pipe(take(1))
  );
}