3

我将主题作为可观察对象从服务传递给解析器。当一个 http 请求完成或当它发现它需要的数据被缓存时,服务可能会完成 observable。在第二种情况下,数据被缓存,看起来 observable 完成得太早了,因为路由转换没有发生。如果我将 requestSubject.complete() 放在 setTimeout 中并有一些超时,它可以工作,否则它不会。

//resolve function from resolver
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
  const id = +route.paramMap.get('id');
  const language = route.queryParamMap.get('language');
  const request = this.service.get(id, language);
  request.pipe(share()).subscribe(
    () => {},
    () => { this.router.navigateByUrl(AppRoutingConfig.search); });
  return request;
}


//the service get function
get(id: number, language: string): Observable<any> {
  const requestSubject = new Subject();
  if (!isDataCached(id)) {
    const url = `some url`;
    const sub = this.http.get(url).subscribe((item: any) => {
      requestSubject.next(1);
    }, () => {
        requestSubject.error(0);
    }, () => {
      requestSubject.complete();
    });
  } else {
    requestSubject.complete();
  }
  return requestSubject.asObservable();
}
4

2 回答 2

3

看起来 observable 完成得太早了,因为路由转换没有发生

我会说主题没有做任何事情。

如果数据未缓存,requestSubject.complete();将运行,它将同步向所有订阅者发出完整的通知。

这意味着返回requestSubjectafter 不会做任何事情,因为requestSubject不会发出值。

它在数据未缓存时起作用,因为在 Subject 发出并完成时,它已经有一个可以决定路由转换是否应该继续的订阅者。

例如,这是 Angular 在内部订阅resolve属性中提供的所有可观察对象的方式:

return from(keys).pipe(
      mergeMap(
          (key: string) => getResolver(resolve[key], futureARS, futureRSS, moduleInjector)
                               .pipe(tap((value: any) => {
                                 data[key] = value;
                               }))),
      takeLast(1),
      mergeMap(() => {
        // Ensure all resolvers returned values, otherwise don't emit any "next" and just complete
        // the chain which will cancel navigation
        if (Object.keys(data).length === keys.length) {
          return of(data);
        }
        return EMPTY;
      }),
  );

来源,

如您所见,如果resolve()fn 没有发出任何值,EMPTY则会发出一个 observable,这反过来会导致导航被取消。


我还要修改一下你的resolve功能:

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
  const id = +route.paramMap.get('id');
  const language = route.queryParamMap.get('language');
  const request$ = this.service.get(id, language);

  return request$.pipe(
    tap(
      null, 
      err => this.router.navigateByUrl(AppRoutingConfig.search),
    ),
  );
}

我认为share()没有必要,因为您只有一个订阅者


所以,一个快速修复(我根本不推荐)我认为在这种情况下也发送缓存值:

// Inside `service.get()`

else {
  // The value is cached

  of(getCachedValue(id)).pipe(
    subscribeOn(asyncScheduler) // delay(0) would also work
  ).subscribe(v => {
    requestSubject.next(v)
    requestSubject.complete()
  })
}

return requestSubject.

另一种解决方案是考虑在interceptors级别添加缓存层,因为我认为这将大大简化您的代码。如果您决定切换到这种方法,您的resolve()fn 可以返回类似的this.service.get(...)内容,因为拦截器会拦截所有内容,因此您可以从那里返回缓存的值。

于 2020-05-31T15:28:48.443 回答
2

在您的情况下,Subject您不需要使用ReplaySubject,它会记住最后一个值,并在有人订阅后发出它。

不过,我会更改get()为流使用缓存状态。

//resolve function from resolver
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
  const id = +route.paramMap.get('id');
  const language = route.queryParamMap.get('language');
  return this.service.get(id, language).pipe(
    catchError(() => {
      this.router.navigateByUrl(AppRoutingConfig.search);
      return EMPTY; // to suppress the stream
    }),
  );
}


//the service get function
private readonly cache = new Map(); // our cache for urls


get(id: number, language: string): Observable<any> {
  const url = `some url`;

  // if we had the id - we return its observable.
  if (this.cache.has(id)) {
    return cache.get(id);
  }

  // otherwise we create a replay subject.
  const status$ = new ReplaySubject(1);
  this.cache.set(id, status$);

  this.http.get(url).subscribe(
    () => status$.next(),
    e => status$.error(e),
    () => requestSubject.complete(),
  );

  return status$.asObservable();
}
于 2020-06-01T10:21:22.563 回答