rxjs + angular:错误“杀死”可观察到的Web服务调用中的错误

时间:2018-10-30 22:47:49

标签: angular rxjs

因此,我开始使用rxjs,并且遇到了有关从Web服务调用中获取错误后保持我的可观察内容存活的最佳方法的问题。

在显示代码之前,这是我当前的情况:一个角度组件,必须加载已分页的初始列表,并且还可以通过更改组合上的项目进行过滤。为了使用rxjs解决此问题,我考虑过合并两个可观察对象:一个处理选择的change事件,另一个用于加载更多项。这是我正在使用的代码:

const filtro$ = this.estadoPedido.valueChanges.pipe(
    distinctUntilChanged(),
    tap(_ => {
        this._paginaAtual = 0;
        this.existemMais = true;
    }),
    startWith(this.estadoPedido.value),
    map(estado => new DadosPesquisa(this._paginaAtual,
        this._elemsPagina,
        estado,
        false))
);

每当选择更改时,我最终都会重置全局页面计数器(tap运算符),并且由于我想进行初始加载,因此我也使用了startWith运算符。最后,我将当前状态转换为具有所有必需值以加载值的对象。

我还有一个主题,每当单击“加载更多项目”按钮时,都会使用该主题:

dataRefresh$ = new Subject<DadosPesquisa>();

这两个可观察对象合并在一起,这样我就可以使用一条路径来调用Web服务:

this.pedidosCarregados$ = merge(filtro$, this.dataRefresh$).pipe(
    tap(() => this.emChamadaRemota = true),
    switchMap(info => forkJoin(
        of(info),
        this._servicoPedidos.obtemPedidos(this._idInstancia,
                                info.paginaAtual,
                                info.elemsPagina,
                                info.estado)
    )),
    shareReplay(),
    tap(([info, pedidos]) => this.existemMais = pedidos.length === info.elemsPagina),
    scan((todosPedidos, info) => !info[0].addPedidosToExisting ?
        info[1] :
        todosPedidos.concat(info[1]), []),
    tap(() => this.emChamadaRemota = false),
    catchError(erro => {
        this.emChamadaRemota = false;
        this.trataErro(erro);
        this.existemMais = false;
        return of([]);
    })
);

快速回顾一下我要在此处执行的操作... tap用于设置和清除控制等待微调框(emChamadaRemota)的字段,以及控制是否加载应该显示更多按钮(existemMais)。我在forkJoin中使用了switchMap,因为我需要跨管道访问有关当前搜索的信息。 scan之所以存在,是因为要加载更多项目,应将这些项目添加到上一个加载的页面。

现在,我还在使用拦截器,该拦截器负责设置正确的标头并使用重试策略处理典型的错误(401、503等)。这是intercept方法的代码:

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const headers = this.obtemHeaders();
    const requestClonado = req.clone({ headers });
    return next.handle(requestClonado).pipe(
                    retryWhen(this.retryStrategy()),
                    catchError(err => {
                        console.error(err);
                        let msgErro: string;
                        if(err instanceof HttpErrorResponse && this._servicoAutenticacao.trataErroFimSessao(err)) {
                            msgErro = "A sua sessão terminou. Vai ser redirecionado para a página de login" ;
                        }
                        else if(err.status === 503 ) {
                            msgErro = "O servidor não devolveu uma resposta válida (503).";
                        }
                        else {
                            msgErro = err.error && err.error.message ? err.error.message : "Ocorreu um erro no servidor.";
                        }
                        if(err.status !== 503) {
                            this._logger.adicionaInfoExcecao(msgErro).subscribe();
                        }
                        return throwError(msgErro);
                    }
                ));
} 

现在,问题是:如果我在Web服务调用上遇到错误,一切正常,但是我的可观察对象将被“杀死” ...这是有道理的,因为操作员应捕获错误并“取消订阅”流(至少,这是我从阅读过的一些文章中了解的内容)。

我读过一些文章,说解决方案是创建一个内部的可观察对象,该对象从不抛出并且包装从Web服务调用返回的可观察对象。这是要走的路吗?如果是这样,我可以在拦截器级别执行此操作吗?还是在发生错误的情况下,我是否应该简单地重建我的可观察链(但不要使用startWith运算符自动启动它)?

1 个答案:

答案 0 :(得分:0)

好吧,经过一些测试,我设法使其正常工作的唯一方法(不放弃我所拥有的重试/捕获错误传播)是在引发异常时重建管道。因此,我将创建代码移到了一个方法中:

private setupPipeline(runFirstTime = true) {
  const filtro$ = this.estadoPedido.valueChanges.pipe(
                   distinctUntilChanged(),
                   tap(_ => {
                         this._paginaAtual = 0;
                         this.existemMais = true;
                   }),
                   runFirstTime ? startWith(this.estadoPedido.value) : tap(),
                   map(estado => new DadosPesquisa(this._paginaAtual,
                                                  this._elemsPagina,
                                                  estado,
                                                  false))
                   );
  this.pedidosCarregados$ = merge(filtro$, this.dataRefresh$).pipe( 
       //same as before...
      catchError(erro => {
         this.emChamadaRemota = false;
         this.trataErro(erro);
         this.existemMais = false;
         setTimeout(() => this.setupRxjs(false), 100); // reset pipeline
         return of([]);
    })
}

该方法是从init方法内部以及catchError运算符内部调用的。我敢肯定有更好的方法,但是重新创建管道使我几乎可以按原样重用代码...