Angular HTTP拦截器结合shareReplay与timeout时请求无法正常取消的问题
嘿,这个问题我之前排查过类似的,其实核心就是shareReplay(1)的默认行为和Angular HTTP请求的取消机制撞车了,我给你掰扯清楚:
首先得搞明白shareReplay(1)到底在干嘛:它会缓存源Observable最后1个值,而且默认会一直保留对源Observable的订阅,哪怕所有外部订阅都取消了也不会断。而Angular的HTTP请求(就是next.handle(request)返回的那个Observable),只有当它的所有订阅都被取消时,才会触发浏览器层面的请求取消逻辑。
回到你的场景具体分析:
- 你在组件里给请求加了
timeout(1000),超时后timeout操作符会立即取消当前组件对这个Observable的订阅 - 但拦截器里的
shareReplay(1)会自己维持一个对源HTTP Observable的订阅(用来缓存结果),这就导致源Observable的订阅数并没有降到0 - 所以Angular认为还有人在关注这个请求,就不会触发请求取消的逻辑,浏览器里的请求自然就会一直跑下去
那怎么解决呢?其实很简单,给shareReplay加个配置项就行:
你可以把shareReplay(1)改成shareReplay({bufferSize:1, refCount:true}),这里的refCount:true是关键——它会让shareReplay在最后一个外部订阅取消时,自动取消对源Observable的订阅,这样当timeout触发取消后,源HTTP Observable的订阅数就会降到0,Angular就能正常触发请求取消了。
修改后的拦截器代码是这样的:
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable, shareReplay } from 'rxjs'; @Injectable({ providedIn: 'root', }) export class TokenInterceptorService implements HttpInterceptor { intercept( request: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> { return next.handle(request).pipe(shareReplay({bufferSize:1, refCount:true})); } }
不过这里要提醒你一句:如果你的shareReplay是用来做请求结果缓存的,refCount:true意味着当最后一个订阅取消后,缓存会被清空,下次新的订阅进来会重新发起请求,这个行为你要确认符合你的业务需求。
至于为什么去掉shareReplay就正常?因为没有了这个“顽固”的缓存订阅,当timeout取消组件的订阅后,源HTTP Observable的订阅数直接降到0,Angular就会立刻触发请求取消,一切就回归正常了。
备注:内容来源于stack exchange,提问作者air duck

