例如,我需要从不同来源获取两个相同类型的数组。这两个请求都可能会失败,但是当这些请求同时失败时,我需要显示任何错误(由服务器给出)。
这是一个常见的示例,但当任何请求失败时它都会抛出错误:
func test() async throws -> [Any] {
async let t1 = getArr()
async let t2 = getArr2()
return try await t1 + t2
}
func getArr() async throws -> [Any] {
[]
}
func getArr2() async throws -> [Any] {
[]
}
同时,如果我使用
try?
,我会丢失请求返回的错误。
我也可以删除
throws
并用 Result<[Any], Error>
替换返回结果,但看起来很奇怪。
如何正确解决这个问题?
你绝对应该在这里使用
Result
。为了方便起见,首先编写一个 Result
初始化程序来捕获异步抛出闭包中的错误。
extension Result {
init(asyncCatching block: () async throws -> Success) async where Failure == Error {
do {
self = .success(try await block())
} catch {
self = .failure(error)
}
}
}
假设您希望
test
在所有 async let
抛出时抛出所有错误,请将 [Error]
也设为 Error
类型。
extension Array: Error where Element: Error {}
那么就很简单:
func test() async throws -> [Any] {
async let t1 = Result { try await getArr() }
async let t2 = Result { try await getArr2() }
switch (await t1, await t2) {
case let (.failure(e1), .failure(e2)):
return [e1, e2]
case let (.success(s), .failure), let (.failure, .success(s)):
return s
case let (.success(s1), .success(s2)):
return s1 + s2
}
}
对于“将
Result
的集合减少为单个值或抛出”操作的更通用版本,您可以编写:
func accumulateResults<T, U, Failure: Error>(_ results: [Result<T, Failure>], identity: U, reduce: (U, T) -> U) throws -> U {
var ret = identity
var success = false
var failures = [Failure]()
for result in results {
switch result {
case .success(let s):
ret = reduce(ret, s)
case .failure(let e):
if success { break }
failures.append(e)
}
}
return if success { ret } else { throw failures }
}
如果不使用
Result
(或重新发明类似的东西),test
只能抛出最后一个错误,因为你无法存储任何以前的错误(Result
的确切工作)。
func testWithoutResult() async throws -> [Any] {
async let t1 = getArr()
async let t2 = getArr2()
var result = [Any]()
var success = false
do {
result.append(try await t1)
success = true
} catch {}
do {
result.append(try await t2)
} catch {
if !success { throw error }
}
return result
}
对于每个非最终异步操作,编写
do
块并设置 success = true
。对于最终的异步操作,请改为在 catch 块中if !success { throw error }
。