发布者失败时合并接收器未完成
Combine sink does not complete when publisher fails
我目前正在构建一个 SwiftUI 应用程序并使用来自 github (GitHub/Squid) 的网络库来实现 api 调用。
它工作正常,除非请求失败,然后库内部调试消息指示错误(在我的测试中为 404),但 Combine 订阅者从未收到完成信号。
我创建了一个新请求,将尝试获取和解码 "jsonplaceholder.typicode.com" 的用户。
import Foundation
import Squid
struct GetUsersFromMockApiRequest: JsonRequest {
typealias Result = [User]
var routes: HttpRoute {
["users"]
}
}
用户只是一个简单的结构:
struct User: Decodable {
let id: Int
let username: String
let name: String
}
在我的 ApiRequestManager.swift 文件中,我声明了一个请求调度的函数:
public func getUsersFromMockApi() -> Response<GetUsersFromMockApiRequest> {
return GetUsersFromMockApiRequest().schedule(with: ApiService(apiUrl: "jsonplaceholder.typicode.com"))
}
在我想调用 api 的地方,我有一个可取消集和一个 ApiRequestManager 实例。
@State private var cancellableSet: Set<AnyCancellable> = []
private let requestManager = ApiRequestManager()
现在最后一步是在按下执行以下代码的按钮时调用一个函数:
self.requestManager.getUsersFromMockApi()
.print("Debug:")
.sink(receiveCompletion: { completion in
switch completion {
case .failure:
print("ApiCall failed.")
case .finished:
print("ApiCall finished.")
}
}) { result in
print("Received users from apiCall: \(result)")
}
.store(in: &cancellableSet)
如果api请求成功,一切正常。但是一旦遇到任何错误,接收器就会收到任何信号/完成。
我尝试打印订阅者收到的每条消息,breakOnError 并且还一步一步跟着库看他是否抛出错误。它确实根据 NetworkScheduler.swift 文件中 schedule() 函数中接受的状态代码正确检查 HTTP 响应代码,并在第 285 行抛出自定义错误 throw Squid.Error.requestFailed(statusCode: statusCode, response: response.data)
。
然而,由于我缺乏调试和组合知识,我很难在之后跟踪抛出。
您遇到的问题可能是基础 Cocoa 库(特别是 URLSession)未将 404 视为错误。这似乎是 Squid 库中正在完成(或未完成)的事情,您正在对它提供的内容做出反应。
URLSession 本身仅在无法从服务器获得响应时抛出错误 - 因此当问题与无法解析主机名或建立连接等问题有关时。如果您建立连接并获得响应,URLSession 将不会抛出错误,但状态代码将正确反映 - 它只是 "look" 就像一个成功的请求。
上面的示例没有显示该部分,但是来自 URLSession
的 404 响应发生的情况是请求完成,并且状态代码 404
在响应中编码,但是它不会作为错误状态抛出。
在 Combine 发布者链中处理此问题的典型模式是将 URLSession 的结果数据传递到 tryMap
运算符,您可以在其中进一步检查结果(状态代码、数据等) ) 并确定是否要将其转换为抛出的错误(例如,如果您收到 404 响应)。
您可以在 https://heckj.github.io/swiftui-notes/#patterns-datataskpublisher-trymap 找到这种模式的示例。此模式的要点是允许您根据对结果的检查来定义您想要的任何错误(假设您在这些情况下抛出异常)。
您可以在 Using Combine.
中找到使用 Combine(以及 URLSession.dataTaskPublisher
)的其他示例
我目前正在构建一个 SwiftUI 应用程序并使用来自 github (GitHub/Squid) 的网络库来实现 api 调用。
它工作正常,除非请求失败,然后库内部调试消息指示错误(在我的测试中为 404),但 Combine 订阅者从未收到完成信号。
我创建了一个新请求,将尝试获取和解码 "jsonplaceholder.typicode.com" 的用户。
import Foundation
import Squid
struct GetUsersFromMockApiRequest: JsonRequest {
typealias Result = [User]
var routes: HttpRoute {
["users"]
}
}
用户只是一个简单的结构:
struct User: Decodable {
let id: Int
let username: String
let name: String
}
在我的 ApiRequestManager.swift 文件中,我声明了一个请求调度的函数:
public func getUsersFromMockApi() -> Response<GetUsersFromMockApiRequest> {
return GetUsersFromMockApiRequest().schedule(with: ApiService(apiUrl: "jsonplaceholder.typicode.com"))
}
在我想调用 api 的地方,我有一个可取消集和一个 ApiRequestManager 实例。
@State private var cancellableSet: Set<AnyCancellable> = []
private let requestManager = ApiRequestManager()
现在最后一步是在按下执行以下代码的按钮时调用一个函数:
self.requestManager.getUsersFromMockApi()
.print("Debug:")
.sink(receiveCompletion: { completion in
switch completion {
case .failure:
print("ApiCall failed.")
case .finished:
print("ApiCall finished.")
}
}) { result in
print("Received users from apiCall: \(result)")
}
.store(in: &cancellableSet)
如果api请求成功,一切正常。但是一旦遇到任何错误,接收器就会收到任何信号/完成。
我尝试打印订阅者收到的每条消息,breakOnError 并且还一步一步跟着库看他是否抛出错误。它确实根据 NetworkScheduler.swift 文件中 schedule() 函数中接受的状态代码正确检查 HTTP 响应代码,并在第 285 行抛出自定义错误 throw Squid.Error.requestFailed(statusCode: statusCode, response: response.data)
。
然而,由于我缺乏调试和组合知识,我很难在之后跟踪抛出。
您遇到的问题可能是基础 Cocoa 库(特别是 URLSession)未将 404 视为错误。这似乎是 Squid 库中正在完成(或未完成)的事情,您正在对它提供的内容做出反应。
URLSession 本身仅在无法从服务器获得响应时抛出错误 - 因此当问题与无法解析主机名或建立连接等问题有关时。如果您建立连接并获得响应,URLSession 将不会抛出错误,但状态代码将正确反映 - 它只是 "look" 就像一个成功的请求。
上面的示例没有显示该部分,但是来自 URLSession
的 404 响应发生的情况是请求完成,并且状态代码 404
在响应中编码,但是它不会作为错误状态抛出。
在 Combine 发布者链中处理此问题的典型模式是将 URLSession 的结果数据传递到 tryMap
运算符,您可以在其中进一步检查结果(状态代码、数据等) ) 并确定是否要将其转换为抛出的错误(例如,如果您收到 404 响应)。
您可以在 https://heckj.github.io/swiftui-notes/#patterns-datataskpublisher-trymap 找到这种模式的示例。此模式的要点是允许您根据对结果的检查来定义您想要的任何错误(假设您在这些情况下抛出异常)。
您可以在 Using Combine.
中找到使用 Combine(以及URLSession.dataTaskPublisher
)的其他示例