刷新Token时暂停网络请求的优化方案咨询
解决Token刷新时重复触发网络请求的问题
问题描述
现有如下NetworkService类,功能正常但存在一个问题:在执行Token刷新请求期间,新到来的请求会重复触发Token刷新操作,造成不必要的网络调用。此前尝试过DispatchSemaphore但不适用于异步方法,还会导致请求串行;尝试用数组存储待处理请求但扩展复杂,寻求其他可行解决方案。
原代码
class NetworkService: NetworkRequestServicing { private let tokenService: TokenServicing private let httpClient: HTTPClient weak var delegate: TokenDelegate? public init(httpClient: HTTPClient, tokenService: TokenServicing) { self.httpClient = httpClient self.tokenService = tokenService } func sendAuthenticatedRequest<T: Decodable>(request: Request, responseModel: T.Type, isFormData: Bool, retryTimes: Int, additionalHeaders: [String: String]?) async -> Result<T, RequestError> { let result = await httpClient.sendRequest(request: request, responseModel: responseModel, retryTimes: retryTimes, additionalHeaders: additionalHeaders, isFormData: isFormData) switch result { case .success: return result case .failure(let error): if case RequestError.unauthorized = error { return await refresh(request: request, responseModel: responseModel) } else { return .failure(error) } } } private func refresh<T: Decodable>(request: Request, responseModel: T.Type, isFormData: Bool = false, sendBinaryRequest: ((Request) async -> Result<T, RequestError>)? = nil) async -> Result<T, RequestError> { print("New Refresh Token needed") guard let expiryDate = tokenService.getRefreshExpiryDate(), expiryDate > .now else { print("RefreshToken not valid") return await .failure(reLogin()) } guard tokenService.getRefreshToken() != nil else { print("No refresh token available -> User must ReLogin") return await .failure(reLogin()) } let numberOfRetries = 3 for failedRefreshAttempts in 1...numberOfRetries { let result = await tokenService.tokenRefresh() switch result { case .success: print("Refresh Tokens succeeded") await sendRefreshToDelegates() if let sendBinaryRequest = sendBinaryRequest { return await sendBinaryRequest(request) } else { return await httpClient.sendRequest(request: request, responseModel: responseModel, isFormData: isFormData) } case .failure(let error): print("\(failedRefreshAttempts). attempt of refreshing the tokens failed due to \(error)") } try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second } return await .failure(reLogin()) } }
解决方案:共享刷新任务
核心思路是维护一个全局唯一的刷新任务,当多个请求触发401需要刷新Token时,让所有请求等待同一个刷新任务完成,避免重复发起刷新请求。具体实现如下:
步骤1:添加共享任务管理属性
在NetworkService中添加两个私有属性,用于跟踪刷新任务并保证线程安全:
private var currentRefreshTask: Task<Result<Void, RequestError>>? private let refreshLock = NSLock()
步骤2:重构刷新逻辑,新增协调方法
新增coordinateRefresh方法负责管理刷新任务的创建与共享;将原refresh方法拆分为仅执行刷新操作的performRefresh方法,分离职责。
修改后的完整代码
class NetworkService: NetworkRequestServicing { private let tokenService: TokenServicing private let httpClient: HTTPClient // 新增:管理共享刷新任务与线程安全锁 private var currentRefreshTask: Task<Result<Void, RequestError>>? private let refreshLock = NSLock() weak var delegate: TokenDelegate? public init(httpClient: HTTPClient, tokenService: TokenServicing) { self.httpClient = httpClient self.tokenService = tokenService } func sendAuthenticatedRequest<T: Decodable>(request: Request, responseModel: T.Type, isFormData: Bool, retryTimes: Int, additionalHeaders: [String: String]?) async -> Result<T, RequestError> { let result = await httpClient.sendRequest(request: request, responseModel: responseModel, retryTimes: retryTimes, additionalHeaders: additionalHeaders, isFormData: isFormData) switch result { case .success: return result case .failure(let error): if case RequestError.unauthorized = error { // 等待刷新任务完成后重试原请求 let refreshResult = await coordinateRefresh() switch refreshResult { case .success: return await httpClient.sendRequest(request: request, responseModel: responseModel, retryTimes: retryTimes, additionalHeaders: additionalHeaders, isFormData: isFormData) case .failure(let refreshError): return .failure(refreshError) } } else { return .failure(error) } } } // 新增:协调刷新任务,保证同一时间仅一个刷新请求执行 private func coordinateRefresh() async -> Result<Void, RequestError> { refreshLock.lock() defer { refreshLock.unlock() } // 已有刷新任务时,直接等待其完成 if let existingTask = currentRefreshTask { return await existingTask.value } // 创建新的刷新任务并记录引用 let task = Task { let result = await performRefresh() // 任务完成后清理引用 refreshLock.lock() currentRefreshTask = nil refreshLock.unlock() return result } currentRefreshTask = task return await task.value } // 拆分后仅负责执行Token刷新逻辑 private func performRefresh() async -> Result<Void, RequestError> { print("New Refresh Token needed") guard let expiryDate = tokenService.getRefreshExpiryDate(), expiryDate > .now else { print("RefreshToken not valid") return .failure(reLogin()) } guard tokenService.getRefreshToken() != nil else { print("No refresh token available -> User must ReLogin") return .failure(reLogin()) } let numberOfRetries = 3 for failedRefreshAttempts in 1...numberOfRetries { let result = await tokenService.tokenRefresh() switch result { case .success: print("Refresh Tokens succeeded") await sendRefreshToDelegates() return .success(()) case .failure(let error): print("\(failedRefreshAttempts). attempt of refreshing the tokens failed due to \(error)") } try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second } return .failure(reLogin()) } // 原reLogin方法(假设已实现) private func reLogin() -> RequestError { return RequestError.unauthorized } }
关键逻辑说明
- 共享任务管理:
coordinateRefresh会先检查是否已有正在执行的刷新任务,存在则直接等待;不存在则创建新任务,后续请求复用同一个任务,彻底避免重复刷新。 - 线程安全:用
NSLock保证对currentRefreshTask的读写操作线程安全,避免多线程环境下的竞态问题。 - 异步非阻塞:等待刷新任务的过程是异步的,不会阻塞其他正常请求,仅触发401的请求会等待刷新完成后重试。
- 职责分离:刷新逻辑与任务协调逻辑拆分,代码结构更清晰,便于后续维护扩展。
内容的提问来源于stack exchange,提问作者Mohamed Emad Hegab
相关产品推荐
相关产品推荐

