【问题标题】:All asynchronous calls succeed or none, how to handle异步调用全部成功或不成功,如何处理
【发布时间】:2018-08-28 04:17:15
【问题描述】:

我正在尝试创建一个在线移动应用程序,但找不到处理具有多个异步调用的函数的最佳方法。假设我有一个函数,例如以某种方式更新用户,但在单个函数调用中涉及多个异步调用。比如:

// Function caller
update(myUser) { (updatedUser, error) in
    if let error = error {
       // Present some error UI to the user
    }

    if let updatedUser = updatedUser {
       // Do something with the user
    }
} 


// Function implementation
public func updateUser(user: User, completion: @escaping (User?, Error?) -> () {

    // asynchronous call A
    updateUserTable(user: User) { error in
        if let error = error {
           completion(nil, error)
        } else {
           // create some new user object
           completion(user, nil)
        }
    }

    // asynchronous call B
    uploadMediaForUser(user: User) { error in
        if let error = error {
           completion(nil, error)
        }
    }

    // asynchronous call C
    removeOldReferenceForUser(user: User) { error in
         if let error = error {
           completion(nil, error)
        }
    }

    // Possibly any additional amount of asynchronous calls...
}

在这种情况下,一个函数调用(如更新用户)涉及多个异步调用,这是全有还是全无的情况?例如,updateUserTable() 调用完成,但用户在 uploadMediaForUser() 运行时断开了互联网连接,这会引发错误。由于 updateUserTable() 完成得很好,我的函数调用者认为这个方法成功了,而实际上更新用户所涉及的所有事情都没有完成。现在我遇到了一个用户,该用户可能在我的数据库中存在不匹配的引用或错误信息,因为该用户的连接在通话过程中中断。

我该如何处理这种全有或全无的情况?如果每个异步调用都没有错误地完成,我知道更新用户是成功的。如果只有部分异步调用成功而一些失败,这很糟糕,我需要撤消成功的更改或再次尝试失败的方法。

在这种情况下我该怎么办?而且,我如何使用我的完成闭包来帮助识别所需的操作,具体取决于方法的成功或失败。他们都成功了吗?好,告诉用户。有的成功有的失败了吗?不好,还原更改或重试(我不知道)??

编辑:

仅仅用错误来调用我的完成似乎还不够。当然,用户看到某事失败了,但这无助于应用程序了解修复部分更改损坏所需的步骤。

【问题讨论】:

    标签: swift asynchronous closures


    【解决方案1】:

    我建议为您的任务和返回的结果添加辅助枚举,例如 (User?, Error?) 之类的情况有点模糊,例如两者都为零?或者你设置了用户和错误,是成功还是失败?

    关于全部成功或部分失败 - 我建议使用 DispatchGroup 通知所有任务何时完成(并检查它们最终是如何完成的)。

    同样从您当前的代码来看,当某些请求失败时,不清楚哪个用户 - 因为您传递 nil,因此在失败后回滚可能会带来困难。

    因此,在我看来,如下所示(未测试代码,但认为您应该从中了解想法)可以让您控制所描述的问题:

    public enum UpdateTask {
        case userTable
        case mediaUpload
        // ... any more tasks you need
    }
    
    public enum UpdateResult {
        case success
        case error([UpdateTask: Error])
    }
    
    // Function implementation
    public func updateUser(user: User, completion: @escaping (User, UpdateResult) -> ()) {
    
        let updateGroup = DispatchGroup()
        var tasksErrors = [UpdateTask: Error]()
    
        // asynchronous call A
        updateGroup.enter()
        updateUserTable(user: User) { error in
            if let error = error {
                tasksErrors[.userTable] = error
            }
            updateGroup.leave()
        }
    
        // ... any other similar tasks here
    
        updateGroup.notify(queue: DispatchQueue.global()) { // Choose the Queue that suits your needs here by yourself
            if tasksErrors.isEmpty {
                completion(user, .success)
            } else {
                completion(user, .error(tasksErrors))
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      保留所有更改的“先前”版本,然后如果出现故障,则恢复为“先前”版本。仅在所有返回且未失败的情况下更改 UI,如果失败,则恢复为“先前”版本。

      前:

      var temporary = “userName”
      getChanges(fromUser) {
      If error {
         userName = temporary //This reverts back due to failure.
        }
      }
      

      【讨论】:

        猜你喜欢
        • 2023-03-08
        • 2015-03-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-11
        • 2012-11-01
        • 2020-02-05
        • 2015-03-08
        相关资源
        最近更新 更多