所有异步调用成功或无成功,如何处理

时间:2018-03-19 14:49:07

标签: swift asynchronous closures

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

// 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()运行时,用户断开了与Internet的连接,并且抛出了一个错误。由于updateUserTable()完成得很好,我的函数调用者认为这个方法成功了,事实上并非所有涉及更新用户的内容都已完成。现在我遇到的用户可能在我的数据库中有不匹配的引用或错误的信息,因为用户的连接在调用过程中丢失了。

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

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

编辑:

只是用错误调用我的完成似乎不够。当然用户会看到某些内容失败,但这对应用程序知道修复部分更改所造成的损坏所需的步骤没有帮助。

2 个答案:

答案 0 :(得分:0)

我建议为你的任务添加辅助枚举并返回结果,像(User?,Error?)之类的东西有一点不明确的情况,例如两者都是nil?或者您设置了用户和错误,是否成功?

关于全部成功或一些失败 - 我建议使用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))
        }
    }
}

答案 1 :(得分:0)

保持所有内容的“先前”版本更改,然后如果某些内容失败则恢复为“之前”版本。只有在没有失败的情况下返回后才更改UI,如果一个失败,则恢复为“之前”版本。

EX:

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