Angular 7 RxJs - HTTP 调用未在 ForkJoin 中完成

Posted

技术标签:

【中文标题】Angular 7 RxJs - HTTP 调用未在 ForkJoin 中完成【英文标题】:Angular 7 RxJs - HTTP calls do not complete in ForkJoin 【发布时间】:2019-12-14 10:05:53 【问题描述】:

我正在尝试使用 RxJs forkJoin 组合多个 HTTP 请求。当我订阅 observable 时,会进行 HTTP 调用,但没有返回任何内容。我尝试用通用的 observables (of('hello')) 替换 HTTP 调用,并且订阅会收到结果。

HTTP 调用似乎没有“完成”,但查看 DevTools,我看到了请求(响应 200)及其响应数据。

  test() 
    const simple = [
      of('Hello'),
      of('World'),
      this.http.get('https://jsonplaceholder.typicode.com/todos/1')
    ];

    forkJoin(simple)
      .subscribe(result => 
        console.log("Test", result);
      , (err) => 
        console.warn("Test Error", err);
      );
  

如果我注释掉 HTTP 调用,我会在控制台中记录以下内容: Test result: ["Hello", "World"]

但是通过 HTTP 调用,控制台不会记录任何内容。

最后 - 我检查了以下 HTTP 调用:

    this.http.get('https://jsonplaceholder.typicode.com/todos/1')
      .subscribe(result => 
        console.log('checking', result);
      );

它成功地将结果记录到控制台。

更新 我已将问题追溯到我们的 HttpInterceptor,它添加了 Authorization 标头。拦截器依赖 AuthService 获取 JWT 令牌,并且 AuthService 必须等到 ConfigService 从远程 JSON 文件检索应用程序配置。

注意:由于 ConfigService 必须发出 HTTP 请求,因此我们已将拦截器配置为仅在已设置 Authorization 标头的情况下传递请求。这样,我们可以绕过 HttpInterceptor 尝试添加 JWT 来伪造 ConfigService 中的 Authorization 标头。

@Injectable()
export class AuthTokenInterceptorService implements HttpInterceptor 

  constructor(
    private authService: AuthService
  )  

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 

    if (request.headers.has(AUTHORIZATION_HEADER_KEY)) 
      // do not try to add authorization header
      const dup = request.clone();
      return next.handle(dup);
    

    // else
    return this.authService.ready$()
      .pipe(
        filter((ready: boolean) => !!ready),
        switchMap((ready: boolean) => 
          if (ready) 
            const clone = request.clone(
              setHeaders: 
                Authorization: `Bearer $this.authService.getToken()`
              
            );
            return next.handle(clone);
          

          // else ... filter should only pass when ready === true
          console.warn('OOPS -> TokenInterceptor could not add the authorization token as the AuthService was not ready');
        )
      );
  

AuthService 的 ready$ 属性:

  ready$() 
    return !!this.configService.ready$ ? this.configService.ready$ : of(false);
  

还有 ConfigService 的 ready$ 属性:

  private _config: IConfig;
  private readySubject = new ReplaySubject<boolean>();
  ready$: Observable<boolean>;
  ...
  constructor(private http: HttpClient) 
    this.ready$ = this.readySubject.asObservable();
    this.readySubject.next(!!this._config);
  
  ...
  load() 
    const fakeAuth = new HttpHeaders().set(AUTHORIZATION_HEADER_KEY, 'faked');

    return this.http.get(this.CONFIG_URL,  headers: fakeAuth )
      .pipe(
        map((result) => 
          this._config = ...DefaultConfig, ...result as IConfig;
          return this._config;
        ),
        tap(() => 
          this.readySubject.next(!!this._config);
        )
      );
  

如果我修改 forkJoin test() 使 HTTP 请求具有 Authorization 标头,则 HTTP 请求完成并且 forkJoin 完成。

  test() 
    const fakeAuth = new HttpHeaders().set(AUTHORIZATION_HEADER_KEY, 'faked');

    const simple = [
      of('Hello'),
      of('World'),
      this.http.get('https://jsonplaceholder.typicode.com/todos/1',  headers: fakeAuth )
    ];

    forkJoin(simple)
      .subscribe(result => 
        console.log("Test", result);
      , (err) => 
        console.warn("Test Error", err);
      );
  

因此,问题似乎出在我的 HttpInterceptor 和/或我用来确定 AuthService 和 ConfigService 何时“准备好”的逻辑中。

也许这太含糊了,但是有人看到我处理 HttpInterceptor(和 AuthService、ConfigService)的方式有问题吗?

解决方法

我能够找到一种解决方法...问题似乎是当我在 HttpInterceptor 中返回一个 observable 时:return this.authService.ready$()...

所以我在 AuthService 中创建了一个标志(属性),当服务准备好提供 JWT 令牌时,该标志设置为 true。所以现在,HttpInterceptor 看起来像这样:

@Injectable()
export class AuthTokenInterceptorService implements HttpInterceptor 

  constructor(
    private authService: AuthService
  )  

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 

    if (request.headers.has(AUTHORIZATION_HEADER_KEY)) 
      // do not try to add authorization header
      const dup = request.clone();
      return next.handle(dup);
    

    if (this.authService.ready) 
      const clone = request.clone(
        setHeaders: 
          Authorization: `Bearer $this.authService.getToken()`
        
      );
      return next.handle(clone);
    

    // else
    console.warn("AuthTokenInterceptorService - waiting for AuthService to be ready...", ready: this.authService.ready);

    return this.authService.ready$()
      .pipe(
        filter((ready: boolean) => !!ready),
        switchMap((ready: boolean) => 
          if (ready) 
            const clone = request.clone(
              setHeaders: 
                Authorization: `Bearer $this.authService.getToken()`
              
            );
            return next.handle(clone);
          

          // else ... filter should only pass when ready === true
          console.warn('OOPS -> TokenInterceptor could not add the authorization token as the AuthService was not ready');
        )
      );
  


所有需要带有 Authorization 标头的 HTTP 请求的路由都受到 AuthGuard 的保护,以确保 AuthService 在 canActivate 为真之前准备就绪。因此,HttpInterceptor 将使用已经初始化的 AuthService(准备好 === true)。

这似乎是一个 hack,因为 HttpInterceptor 依赖于 AuthGuard,所以如果有人有更好的 HttpInterceptor 实现,我很想听听。谢谢

【问题讨论】:

你能在 stackblitz 上重现吗? 我刚刚构建了一个快速堆栈闪电战,这段代码对我有用:stackblitz.com/edit/angular-stgdrj 它记录了一个包含 3 个项目的数组。 这很奇怪。方法 (test()) 从服务中的构造函数中调用。我会尝试在 stackblitz 上重现... 从服务调用时有效:stackblitz.com/edit/angular-qf9v6n 明天,我将移植更多代码以尝试重现错误 【参考方案1】:

如果您要在 Angular 的初始化过程中获取配置,它可能会更好。见Completely external constants in Angular 5 using some configuration file

【讨论】:

感谢@GreyBeardedGeek。我在初始化期间确实得到了我的配置,但保留了 ready$ 可观察的“以防万一”。我一直在使用我的解决方法(假设配置服务已经初始化)并且没有发现需要 ready$ observable 的情况。我很感激(和其他人一样)您在初始化期间配置应用程序的链接。

以上是关于Angular 7 RxJs - HTTP 调用未在 ForkJoin 中完成的主要内容,如果未能解决你的问题,请参考以下文章

RxJS - BehaviorSubject,onComplete 未调用

如何在 Angular 和 RxJs 中管理多个顺序订阅方法?

Angular 7 rxjs BehaviorSubject 发出重复值

Angular/RxJS 6:如何防止重复的 HTTP 请求?

如何使用 RxJS 在 Angular 6 中发出一系列 http 请求

如何使用 Angular Http Interceptor 和 RxJS 刷新 JWT 令牌?