我正在将应用程序的网络转换为使用warec/await,在找到一个令人满意的模式来替换我们之前使用的完成处理程序时遇到了一些麻烦.以下是我们所做的一些伪代码:

func loadData() {
    fetchData {
        showTheSearchField()
    }
}

func fetchData(completion: () -> ()) {
    doTheActualNetworking()
    if networkingSucceeded {
        completion()
    } else {
        putUpAnAlertWithATryAgainButton(buttonAction: {
            fetchData(completion: completion)
        }
    }
}

你明白重点了.首先,我们try 获取数据,这是通过执行实际联网来实现的.然后:

  • 如果我们成功了,如果我们成功了,only,那么在成功的情况下做呼叫者想要做的任何事情(我称之为completion,但也许更好的名字是onSuccess).

  • 如果我们失败了,用一个重试按钮发出警告,该按钮的作用是获取数据again,并以同样的方式again的行为-如果我们这次成功,only如果我们这次成功,那么在成功的情况下执行调用者想要做的任何事情.

我的问题是,当我转换为Await时,我找不到一种方法来消除被炸毁的完成处理程序.例如,在我的示例中,完成处理程序说要显示搜索字段.我不能简单地把整件事概括为例如一个抛出的方法:

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中的任务,然后继续显示搜索字段.但如果我们失败并发出alert ,用户点击重试按钮,这次我们成功了,我们won't显示搜索字段,因为我们失败并抛出和loadData结束.

因此,在我看来,由于重试按钮的循环性质,我是forced来维护完成/成功处理程序,将其传递给fetchData,以便重试按钮可以与其一起递归,就像我之前所做的那样.

当然,这很好用,但我觉得这是一种反模式.我之所以采用异步/等待,正是因为我希望在有生之年再也不想看到另一个完成处理程序.我只是被完成处理程序卡住了,还是有什么我看不到的解决方法?

推荐答案

一种方法是遵循您的递归模式:

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,例如,要将alert 包装在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: .cancel) { _ in
            continuation.resume(throwing: CancellationError())
        })
        present(alert, animated: true)
    }
}

或者,只需将当前基于完成处理程序的函数包装在延续中:

func putUpAnAlertWithATryAgainButton() async {
    await withCheckedContinuation { continuation in
        putUpAnAlertWithATryAgainButton {
            continuation.resume()
        }
    }
}

就我个人而言,我倾向于给"重试"用户界面一个"取消"选项(这样,如果用户碰巧处于无网络环境中,他们就不会陷入无休止的循环),然后给完成处理程序一个参数,指示他们点击哪个按钮的结果.在这种情况下,我们将使用抛出延续,就像UIAlertController个例子,但归根结底,这个应用程序是否真的完全依赖于是否存在正常运行的网络连接.显然,如果没有"Cancel"选项,它将是一个非抛出的继续,您将删除调用中的try,但 idea 是相同的.

关于这个主题有很多变化,但希望它能说明这个 idea .

Swift相关问答推荐

计算Vision OS相机与Vision OS中3D模型之间的距离

启用完整并发判断以及如何解决警告

Swift UI在视图中排序不同的 struct

SwiftUI—如何识别单词并获取视觉中的位置

如何在自定义视图中读取修改符子元素?

SwiftUI:为什么@State在子视图中持续存在?

如果通过计时器循环运行,则检索CPU利用率百分比的SWIFT脚本运行良好.似乎在没有计时器的情况下停留在初始百分比上

可以';t在标记为@Observable的类上使用属性包装

使用 @resultBuilder 的通用 buildList 函数

DispatchQueue.main.asyncAfter 等同于 Swift 中的 struct 化并发?

在 RealmSwift 中表示范围值?

deinitialize() 与 deallocate()

SwiftUI 动画的范围

LeetCode 249. 分组移位字符串

在 Vapor 4 中使用协议的通用流利查询

我们如何显示关于 UITextField 是 xCode Swift 的建议?

在 Swift 中返回实例类型

为什么 swift 中的类没有存储类型属性?

如何将多个枚举值作为函数参数传递

无法将 NSAttributedString.DocumentAttributeKey 类型的值转换为 .DocumentReadingOptionKey