Angular 6拦截器中如何让getTokenFor同一时间仅执行一次?
问题描述
我在Angular 6中使用拦截器为HTTP请求添加API令牌,当请求返回401错误时会刷新令牌。同时有一个负责获取并缓存令牌的服务,现有拦截器代码如下:
public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // some business logic to get e.g. clientId (depends on request parameters) return from(this.getTokenFor(clientId, requestedScope, forceRefresh)).pipe( flatMap(token => this.handleRequest(token, request, next)), catchError(error => { // If this is a cached token, it could be expired/revoked // Try refreshing and retrying the request below, if this could be the case if (error.status !== 401 || forceRefresh) { return throwError(error); } return from(this.getTokenFor(clientId, requestedScope, true)) .pipe(flatMap(token => this.handleRequest(token, request, next))); })); }
其中this.handleRequest负责添加Authorization请求头并将事件转发给next。现在需要实现:即使同时有多个不同的HTTP请求,getTokenFor(返回字符串类型Promise)同一时间也只能被调用一次,后续请求需等待前一次调用完成后再执行,请问该如何实现?
解决方案
核心是通过缓存正在执行的令牌请求Observable,让相同参数的后续请求共享同一请求结果,避免重复调用getTokenFor。
1. 在令牌服务中实现请求去重逻辑
在你的令牌服务里维护一个Map,存储当前正在进行的请求Observable,用clientId+requestedScope作为唯一标识键:
import { Injectable } from '@angular/core'; import { Observable, from, throwError } from 'rxjs'; import { tap, catchError } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class TokenService { private pendingRequests = new Map<string, Observable<string>>(); async getTokenFor(clientId: string, requestedScope: string, forceRefresh: boolean): Promise<string> { const requestKey = `${clientId}:${requestedScope}`; // 非强制刷新时,复用正在进行的请求 if (!forceRefresh && this.pendingRequests.has(requestKey)) { return this.pendingRequests.get(requestKey).toPromise(); } // 创建新的请求Observable const token$ = from(this.fetchToken(clientId, requestedScope, forceRefresh)).pipe( tap(() => this.pendingRequests.delete(requestKey)), catchError(err => { this.pendingRequests.delete(requestKey); return throwError(err); }) ); this.pendingRequests.set(requestKey, token$); return token$.toPromise(); } // 原有的令牌获取逻辑抽离到这里 private async fetchToken(clientId: string, requestedScope: string, forceRefresh: boolean): Promise<string> { // 这里写你原本获取令牌的代码:接口请求、缓存读取等 } }
2. 拦截器代码无需修改
你的现有拦截器逻辑可以直接复用,因为getTokenFor内部已经处理了请求去重——当多个请求同时调用getTokenFor时,只有第一个会触发实际的令牌请求,后续请求会等待第一个请求完成后共享结果。
核心细节
- 请求标识键:如果
getTokenFor的其他参数也会影响令牌结果,需要把这些参数加入到requestKey的生成中,确保不同参数的请求不会互相干扰。 - 强制刷新处理:当
forceRefresh为true(比如401重试场景),会跳过缓存,直接发起新请求,符合原有业务逻辑。 - 错误清理:无论请求成功还是失败,都要从Map中移除对应的Observable,避免后续请求拿到无效的缓存请求。
内容的提问来源于stack exchange,提问作者benyogyerek
相关产品推荐
相关产品推荐

