我正在将应用程序的网络转换为使用异步/等待,但在想出一个令人满意的模式来替换我们之前使用的完成处理程序时遇到了一些麻烦。这是我们所做的一些伪代码:
func loadData() {
fetchData {
showTheSearchField()
}
}
func fetchData(completion: () -> ()) {
doTheActualNetworking()
if networkingSucceeded {
completion()
} else {
putUpAnAlertWithATryAgainButton(buttonAction: {
fetchData(completion: completion)
}
}
}
你明白这里的重点了。首先,我们尝试通过执行实际的网络来获取数据。然后:
如果我们成功了,并且仅如果我们成功了,那么在成功的情况下执行调用者想要做的任何事情(我称之为
completion
,但也许更好的名字是onSuccess
)。
如果我们失败了,请使用“重试”按钮发出警报,该按钮的操作是“再次”获取数据并以同样的方式“再次” — 如果我们这次成功,并且“仅”如果这次我们成功了,那么如果成功的话,就做调用者想做的事情。
我的问题是,当我转换为异步等待时,我找不到一种方法来消除被破坏的完成处理程序。例如,在我的示例中,完成处理程序表示显示搜索字段。我不能把整个事情总结为例如一个抛出异步方法:func loadData() {
Task {
try await fetchData()
showTheSearchField()
}
}
func fetchData() async throws {
do {
try await doTheActualNetworking()
} catch {
putUpAnAlertWithATryAgainButton(buttonAction: {
Task {
try await fetchData()
}
}
throw error
}
}
你知道这有什么问题吗?如果我们第一次在
doTheActualNetworking
上成功,很好,我们会回到 loadData
中的任务,然后继续显示搜索字段。但是,如果我们失败并发出警报,并且用户点击重试按钮,并且这次我们成功了,我们不会
显示搜索字段,因为我们失败并抛出并且
loadData
结束了。
所以在我看来,由于重试按钮的循环性质,我似乎是被迫,为了维护完成/成功处理程序,将其传递到
fetchData
,以便重试按钮可以随之递归,只是就像我之前做的那样。
嗯,当然,这很好用,但对我来说这感觉像是一种反模式。我采用 async/await 正是因为我不想在有生之年看到另一个完成处理程序。我只是坚持使用完成处理程序,还是有一种我没有看到的解决方法?
一种方法是遵循递归模式:
func loadData() async throws {
try await fetchData()
showTheSearchField()
}
func fetchData() async throws {
do {
try await doTheActualNetworking() // assume throws error if no network
} catch NetworkingError.noNetwork {
try await putUpAnAlertWithATryAgainButton() // assume returns if user tapped “try again”, but throws some “user cancelled error” (or perhaps even `CancellationError`) if user taps on “cancel”
try await fetchData()
} catch {
throw error // for whatever errors you do not want a “retry” option, if any
}
}
或者,显然,您也可以非递归地执行此操作(也许也添加一些“最大重试”逻辑;这取决于您):
func fetchData() async throws {
while true {
do {
try await doTheActualNetworking() // assume throws error if no network
return // if no error thrown, just return
} catch NetworkingError.noNetwork {
try await putUpAnAlertWithATryAgainButton() // assume returns if user tapped “try again”, but throws some “user cancelled error” (or perhaps even `CancellationError`) if user taps on “cancel”
} catch {
throw error // for whatever errors you do not want a “retry” option, if any
}
}
}
FWIW,例如,要将警报包装在 UIKit 的延续中,它可能看起来像:
func putUpAnAlertWithATryAgainButton() async throws {
try await withCheckedThrowingContinuation { continuation in
let alert = UIAlertController(title: nil, message: "There was a network error.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Try again", style: .default) { _ in
continuation.resume()
})
alert.addAction(UIAlertAction(title: "Cancel", style: .default) { _ in
continuation.resume(throwing: CancellationError())
})
present(alert, animated: true)
}
}