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 请求?