使用 Combine 的 tryMap 保留失败类型
Preserving Failure Type with Combine's tryMap
我正在使用 Combine 编写一个简单的网络抓取工具。我正在尝试将返回的数据映射到 HTML 的字符串,在每个可能的故障点处抛出 ScraperError
。最后,我想把这个字符串传给我的htmlSubject
,也就是一个PassthroughSubject<String, ScraperError>
,做进一步的处理。
urlSubscription = URLSession.shared
.dataTaskPublisher(for: url)
.mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
ScraperError.unreachableSite
}
.tryMap { (data, response) -> String in
guard let html = String(data: data, encoding: .utf8) else {
throw ScraperError.readFailed
}
return html
}
.subscribe(htmlSubject) // <-- Not allowed because failure type is now Error
但是,我发现 .tryMap
正在将我的 ScraperError
擦除为常规 Error
,从而阻止我将 htmlSubject
链接到末尾:
Instance method 'subscribe' requires the types 'Error' and
'ScraperError' be equivalent.
是否有明显的解决方法我错过了,或者我在概念上被绊倒了?我将此链视为将 <(Data, URLResponse), URLError>
映射到 <String, ScraperError>
的大型函数中的构建块。
感谢任何帮助。
在 tryMap
:
之后使用 mapError
转换回 ScraperError
urlSubscription = URLSession.shared
.dataTaskPublisher(for: url)
.mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
ScraperError.unreachableSite
}
.tryMap { (data, response) -> String in
guard let html = String(data: data, encoding: .utf8) else {
throw ScraperError.readFailed
}
return html
}
.mapError { [=10=] as! ScraperError }
.subscribe(htmlSubject)
如果您不想使用 as!
,则必须选择其他情况映射到:
.mapError { [=11=] as? ScraperError ?? ScraperError.unknown }
如果您也不喜欢,可以使用 flatMap
而不是 Result<String, ScraperError>.Publisher
:
urlSubscription = URLSession.shared
.dataTaskPublisher(for: url)
.mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
ScraperError.unreachableSite
}
.flatMap { (data, response) -> Result<String, ScraperError>.Publisher in
guard let html = String(data: data, encoding: .utf8) else {
return .init(.readFailed)
}
return .init(html)
}
.subscribe(htmlSubject)
我发现将 Rob 的 flatMap
方法包装到扩展中时,生成的代码更具可读性:
extension Publisher {
func flatMapResult<T>(_ transform: @escaping (Self.Output) -> Result<T, Self.Failure>) -> Publishers.FlatMap<Result<T, Self.Failure>.Publisher, Self> {
self.flatMap { .init(transform([=10=])) }
}
}
上面的代码示例将变为:
urlSubscription = URLSession.shared
.dataTaskPublisher(for: url)
.mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
ScraperError.unreachableSite
}
.flatMapResult { (data, response) -> Result<String, ScraperError> in
guard let html = String(data: data, encoding: .utf8) else {
return .failure(.readFailed)
}
return .success(html)
}
.subscribe(htmlSubject)
我正在使用 Combine 编写一个简单的网络抓取工具。我正在尝试将返回的数据映射到 HTML 的字符串,在每个可能的故障点处抛出 ScraperError
。最后,我想把这个字符串传给我的htmlSubject
,也就是一个PassthroughSubject<String, ScraperError>
,做进一步的处理。
urlSubscription = URLSession.shared
.dataTaskPublisher(for: url)
.mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
ScraperError.unreachableSite
}
.tryMap { (data, response) -> String in
guard let html = String(data: data, encoding: .utf8) else {
throw ScraperError.readFailed
}
return html
}
.subscribe(htmlSubject) // <-- Not allowed because failure type is now Error
但是,我发现 .tryMap
正在将我的 ScraperError
擦除为常规 Error
,从而阻止我将 htmlSubject
链接到末尾:
Instance method 'subscribe' requires the types 'Error' and 'ScraperError' be equivalent.
是否有明显的解决方法我错过了,或者我在概念上被绊倒了?我将此链视为将 <(Data, URLResponse), URLError>
映射到 <String, ScraperError>
的大型函数中的构建块。
感谢任何帮助。
在 tryMap
:
mapError
转换回 ScraperError
urlSubscription = URLSession.shared
.dataTaskPublisher(for: url)
.mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
ScraperError.unreachableSite
}
.tryMap { (data, response) -> String in
guard let html = String(data: data, encoding: .utf8) else {
throw ScraperError.readFailed
}
return html
}
.mapError { [=10=] as! ScraperError }
.subscribe(htmlSubject)
如果您不想使用 as!
,则必须选择其他情况映射到:
.mapError { [=11=] as? ScraperError ?? ScraperError.unknown }
如果您也不喜欢,可以使用 flatMap
而不是 Result<String, ScraperError>.Publisher
:
urlSubscription = URLSession.shared
.dataTaskPublisher(for: url)
.mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
ScraperError.unreachableSite
}
.flatMap { (data, response) -> Result<String, ScraperError>.Publisher in
guard let html = String(data: data, encoding: .utf8) else {
return .init(.readFailed)
}
return .init(html)
}
.subscribe(htmlSubject)
我发现将 Rob 的 flatMap
方法包装到扩展中时,生成的代码更具可读性:
extension Publisher {
func flatMapResult<T>(_ transform: @escaping (Self.Output) -> Result<T, Self.Failure>) -> Publishers.FlatMap<Result<T, Self.Failure>.Publisher, Self> {
self.flatMap { .init(transform([=10=])) }
}
}
上面的代码示例将变为:
urlSubscription = URLSession.shared
.dataTaskPublisher(for: url)
.mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
ScraperError.unreachableSite
}
.flatMapResult { (data, response) -> Result<String, ScraperError> in
guard let html = String(data: data, encoding: .utf8) else {
return .failure(.readFailed)
}
return .success(html)
}
.subscribe(htmlSubject)