如何使用Publishers串行链式调用服务请求及相关问题
1. 错误原因分析
你的startSession函数声明返回AnyPublisher<SocketManager?, Never>,但上游的getSession返回的是AnyPublisher<Session, Error>——它的Failure类型是Error,而你没有处理这个错误就直接拼接流,导致整个流的Failure类型仍为Error,和你声明的Never不匹配。
另外,Just(socket)本身是不会抛出错误的Never类型Publisher,你后面加的.catch完全多余,因为Just根本不会触发错误回调。
修正后的代码示例:
func startSession(uniqueId: String) -> AnyPublisher<SocketManager?, Never> { getSession(token: uniqueId) .map { response in self.setupSockets(sessionID: response.id) } .catch { _ in Just(nil) } .eraseToAnyPublisher() }
这里用map直接把Session转换成SocketManager?,再用catch捕获上游getSession可能抛出的错误,返回Just(nil),这样整个流的Failure类型就转为Never,符合函数的返回类型声明。
2. 将Failure类型设为Never是否可行?
完全可行。你已经通过catch处理了所有可能的错误,确保流不会因错误终止;加上套接字仅创建一次,后续也没有错误触发的场景,用Never作为Failure类型可以明确告诉调用者:这个Publisher永远不会失败,只会输出一个值后完成。
3. ViewModel中的调用方式是否正确?
这种方式不算标准的串行链式调用——你是在getCredentialsPublisher的sink回调里手动订阅startSession,属于嵌套订阅,不是Combine推荐的流式写法,还会增加维护多个AnyCancellable的成本。
正确的做法是用Combine操作符(比如flatMap)把两个Publisher串成一个单一的流,优化后的代码:
var sessionSetupSubscriber: AnyCancellable? sessionSetupSubscriber = getCredentialsPublisher(userInfo: object) .flatMap { credentials in self.startSession(uniqueId: credentials.id) } .sink(receiveCompletion: { completion in if case .failure(let error) = completion { print("凭证或会话获取失败:\(error)") } }, receiveValue: { socket in self.socketManager = socket // 直接在这里处理B逻辑:凭证获取完成后的其他操作 self.handlePostCredentialsLogic() }) // 单独封装B逻辑 func handlePostCredentialsLogic() { // do something else that needs to be done when credentials are received. }
关于订阅者的问题:不需要为A和B分别使用不同的订阅者。如果B逻辑不需要依赖套接字创建结果,只是需要在凭证获取成功后执行,也可以用handleEvents提前触发:
sessionSetupSubscriber = getCredentialsPublisher(userInfo: object) .handleEvents(receiveOutput: { credentials in // 在这里执行B逻辑 self.handlePostCredentialsLogic() }) .flatMap { credentials in self.startSession(uniqueId: credentials.id) } .sink(receiveCompletion: { completion in // 处理错误 }, receiveValue: { socket in self.socketManager = socket })
内容的提问来源于stack exchange,提问作者Maria

