同步"异步操作", 避免Block多层嵌套造成的"回调地狱"
The problem with async code (without Promises)
Typically, async operations take a completion handler in a form of a block, which is called to provide either a result or an error. To perform more than one async operation, you have to nest the second one inside the completion block of the first one, and also handle an error gracefully. Often such nesting becomes painful to follow or modify:
/// 获取当前用户的所有联系人的头像列表 (每个联系人对应有一个头像)
func getCurrentUserContactsAvatars(_ completion: ([UIImage]?, Error?) -> Void) {
// 1️⃣. 获取当前用户
MyClient.getCurrentUser() { currentUser, error in
guard error == nil else {
completion(nil, error)
return
}
// 2️⃣. 获取联系人列表(当前用户)
MyClient.getContacts(currentUser) { contacts, error in
guard error == nil else {
completion(nil, error)
return
}
guard let contacts = contacts, !contacts.isEmpty() else {
completion([UIImage](), nil)
return
}
// 3️⃣. 创建容器数组
var count = contacts.count
var avatars = [UIImage](repeating: nil, count: count)
var errorReported = false
for (index, contact) in contacts.enumerated() {
// 4️⃣. 遍历, 获取每个联系人的头像
MyClient.getAvatar(contact) { avatar, error in
if (errorReported) {
return
}
// 5️⃣. 过程出现错误, 返回
guard error == nil {
completion(nil, error)
errorReported = true
return
}
if let avatar = avatar {
avatars[index] = avatar
}
count -= 1
if count == 0 {
// 6️⃣. 没有任何错误, block返回数据
completion(avatars.flatMap { $0 }, nil)
}
}
}
}
}
}
Which could be used as:
getCurrentUserContactsAvatars() { avatars, error in
if (error) {
showErrorAlert(error)
} else {
updateAvatars(avatars)
}
}
Promises to the rescue
The code sample above, when converted into promises, could look like the following :
func getCurrentUserContactsAvatars() -> Promise<[UIImage]> {
return MyClient.getCurrentUser().then(MyClient.getContacts).then { contacts in
all(contacts.map(MyClient.getAvatar))
}
}
That’s all!
Now use it like:
getCurrentUserContactsAvatars().then(updateAvatars).catch(showErrorAlert)