You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RxSwift递归Observable实现:令牌过期后重发请求方案求助

我来帮你搞定这个RxSwift里的令牌刷新重试问题!你当前的代码主要有两个问题:一是没有把刷新令牌后重新请求的结果正确传递给最初的观察者,二是嵌套订阅会导致订阅管理混乱、内存泄漏风险。RxSwift提供了专门的操作符来优雅处理这种"失败后重试依赖操作"的场景,下面给你两种靠谱的实现思路:

思路一:用catchError捕获令牌过期错误,刷新后重发请求

首先我们先定义一个自定义错误,把"令牌过期"作为明确的错误类型抛出,这样Rx的错误流能更好地识别和处理:

enum APIError: Error {
    case tokenExpired
    case invalidJSON
    case underlying(Error)
}

然后改造你的请求函数,把令牌过期的情况转为APIError.tokenExpired抛出,再用catchError捕获这个错误,先刷新令牌再重新发起原请求:

func apiRequest(_ urlConvertible: URLRequestConvertible) -> Observable<[String: AnyObject]> {
    return Observable.create { observer -> Disposable in
        let request = Alamofire.request(urlConvertible).responseJSON { response in
            switch response.result {
            case .success(let value):
                // 优先从服务器响应判断令牌是否过期(比如401状态码或返回的错误标识)
                if response.response?.statusCode == 401 || self.isTokenExpired() {
                    observer.onError(APIError.tokenExpired)
                } else {
                    guard let json = value as? [String: AnyObject] else {
                        observer.onError(APIError.invalidJSON)
                        return
                    }
                    observer.onNext(json)
                    observer.onCompleted()
                }
            case .failure(let error):
                observer.onError(APIError.underlying(error))
            }
        }
        // 记得取消请求的逻辑,避免内存泄漏
        return Disposables.create {
            request.cancel()
        }
    }
    .catchError { error -> Observable<[String: AnyObject]> in
        // 只处理令牌过期的错误,其他错误直接抛出
        guard case APIError.tokenExpired = error else {
            return Observable.error(error)
        }
        // 刷新令牌成功后,重新发起原请求
        return self.generateToken()
            .flatMap { _ in
                self.apiRequest(urlConvertible)
            }
    }
}

思路二:用retryWhen实现带次数限制的重试逻辑

如果需要限制令牌刷新的重试次数(比如最多重试2次,避免无限循环),retryWhen会更合适。它能监听错误流,在满足条件时触发原请求的重试:

func apiRequest(_ urlConvertible: URLRequestConvertible) -> Observable<[String: AnyObject]> {
    return Observable.create { observer -> Disposable in
        // 这里的请求逻辑和上面一致,抛出对应的APIError
        let request = Alamofire.request(urlConvertible).responseJSON { response in
            switch response.result {
            case .success(let value):
                if response.response?.statusCode == 401 || self.isTokenExpired() {
                    observer.onError(APIError.tokenExpired)
                } else {
                    guard let json = value as? [String: AnyObject] else {
                        observer.onError(APIError.invalidJSON)
                        return
                    }
                    observer.onNext(json)
                    observer.onCompleted()
                }
            case .failure(let error):
                observer.onError(APIError.underlying(error))
            }
        }
        return Disposables.create {
            request.cancel()
        }
    }
    .retryWhen { errorObservable -> Observable<Void> in
        return errorObservable.flatMapWithIndex { error, retryIndex -> Observable<Void> in
            // 只处理令牌过期错误,且重试次数不超过2次
            guard case APIError.tokenExpired = error, retryIndex < 2 else {
                return Observable.error(error)
            }
            // 刷新令牌后返回Void,触发原请求重试
            return self.generateToken().map { _ in () }
        }
    }
}

额外注意事项

  • 避免重复刷新令牌:如果多个请求同时触发令牌过期,要确保generateToken()只会发起一次请求。可以给generateToken()加上share(replay: 1, scope: .whileConnected),让多个订阅共享同一个请求结果:
    func generateToken() -> Observable<Token> {
        return Observable.create { observer -> Disposable in
            // 你的令牌请求逻辑
        }
        .share(replay: 1, scope: .whileConnected)
    }
    
  • 优先依赖服务器响应判断过期:尽量用API返回的状态码(比如401)来判断令牌过期,而不是本地的时间判断,避免本地和服务器时间不同步导致误判。

内容的提问来源于stack exchange,提问作者Karthik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:03:29