使用Combine实现带泛型返回的FlatMap链式网络请求时flatMap未触发
问题原因
- 核心原因1:
asURLRequest方法内的Future存在多个未触发promise的代码分支,导致发布者长期处于pending状态,没有输出任何值或错误,下游的flatMap自然不会被触发。漏处理的分支包括:- 当
Auth.auth().currentUser为nil(用户未登录)时,没有调用任何promise回调 - 极端场景下Firebase的
getIDToken回调同时返回空token和空error时,没有兜底回调
- 当
- 核心原因2:如果代码中的
cancellables数组是定义在函数内部的局部变量,函数执行结束后数组会被释放,对应的Combine订阅会立刻被取消,还没等异步的token获取完成,管道就已经终止了。
修复方案
- 补全
asURLRequest中的所有分支回调,确保Future一定会输出结果:
func asURLRequest(baseURL: String) -> AnyPublisher<URLRequest, NetworkRequestError> { return Deferred { Future<URLRequest, NetworkRequestError> { promise in if var urlComponents = URLComponents(string: baseURL) { urlComponents.path = "\(urlComponents.path)\(path)" urlComponents.queryItems = queryItemsFrom(params: queryParams) if let finalURL = urlComponents.url { guard let user = Auth.auth().currentUser else { // 补全用户未登录的错误回调 promise(.failure(NetworkRequestError.authenticationError)) return } print("##### final url -> \(finalURL)") user.getIDToken(completion: { (token, error) in if let fbToken = token { var request = URLRequest(url: finalURL) request.httpMethod = method.rawValue request.httpBody = requestBodyFrom(params: body) let defaultHeaders: HTTPHeaders = [ HTTPHeaderField.contentType.rawValue: contentType.rawValue, HTTPHeaderField.acceptType.rawValue: contentType.rawValue, HTTPHeaderField.authentication.rawValue: fbToken ] request.allHTTPHeaderFields = defaultHeaders.merging(headers ?? [:], uniquingKeysWith: { (first, _) in first }) print("##### API TOKEN() SUCCESS: \(defaultHeaders)") promise(.success(request)) return } if let fbError = error { print("##### API TOKEN() ERROR: \(fbError)") promise(.failure(NetworkRequestError.decodingError)) return } // 补全兜底错误回调 promise(.failure(NetworkRequestError.unknownError)) }) } else { promise(.failure(NetworkRequestError.decodingError)) } } else { promise(.failure(NetworkRequestError.decodingError)) } } }.eraseToAnyPublisher() }
注意:你需要在自己的NetworkRequestError枚举中添加authenticationError、unknownError对应的case,或者替换成你已有的错误类型。
- 把
cancellables数组改为所属类的实例属性,保证订阅的生命周期和页面/业务类绑定:
// 正确写法:作为类的属性持有,不要放在函数内部 class YourViewController: UIViewController { var cancellables = [AnyCancellable]() func requestUserData() { struct ReadUser: Request { typealias ReturnType = UserData var path: String var method: HTTPMethod = .get init(_ id: String) { path = "users/\(id)" } } let apiClient = APIClient(baseURL: BASE_URL) apiClient.dispatch(ReadUser(Auth.auth().currentUser?.uid ?? "")) .receive(on: DispatchQueue.main) .sink( receiveCompletion: { result in switch result { case .failure(let error): print("##### Error loading data: \(error)") default: break } }, receiveValue: { value in // 处理返回的用户数据 }) .store(in: &cancellables) } }
内容的提问来源于stack exchange,提问作者Bruno
相关产品推荐
相关产品推荐

