本文使用的 Promises 是谷歌最近開源的輕量,高性能,安全,測試完備的 Promise 框架。https://github.com/google/promises
Swift 關(guān)于 Promise 的優(yōu)秀框架還有 PromiseKit 和 BrightFutures。
什么是Futures & Promises?
Future 和 Promise 其實是一個東西。很多iOS開發(fā)者可能沒有聽說過 Promise。Promise 在 JavaScript 中最為活躍。JavaScript 中大部分代碼都是單線程的,在沒有引入 Promise 時,開發(fā)者一般編寫回調(diào)函數(shù),但在某些特殊的情況下回調(diào)嵌套非常的多,代碼就會變得非常難讀,難以調(diào)試。在 iOS 中,Swift 閉包 closure 反復嵌套閉包(OC 代碼塊 block 嵌套代碼塊)的時候問題也同樣嚴重。通俗的說 Promise 就是鏈的方式對結(jié)果類型閉包的封裝,避免層層閉包重復嵌套難以閱讀。
簡單場景
日常開發(fā)中可能會使用這樣的網(wǎng)絡請求:
- 第三方平臺授權(quán)獲取第三方 key
- 使用 key 登錄獲取并保存 token
- 使用 token 獲取用戶信息
facebookAuth() { result in
switch result {
case .success(let tokenString, let fbUserID):
login(tokenString: tokenString, fbUserID: fbUserID) { result in
switch result {
case .success(let token):
loadUserProfile(token: token) { result in
saveToken(token: token)
switch result {
case .success(let user):
saveUser(user: user)
case .failure(let error):
print("---LoadUserProfile--- Error: \(error)")
}
}
case .failure(let error):
print("---Login--- Error: \(error)")
}
}
case .failure(let error):
print("---FBLogin--- Error: \(error)")
}
}
可以看到即便是將請求方法剝離,使用枚舉enum封裝了網(wǎng)絡請求回調(diào),代碼還是一層套一層,顯然是陷入了回調(diào)地獄。
在使用 Promise 后,代碼看起來像是這樣
facebookAuth()
.then { tokenString, fbUserID in
return login(tokenString: tokenString, fbUserID: fbUserID)
}.then { token in
saveToken(token: token)
return loadUserProfile(token: token)
}.then { user in
saveUser(user: user)
}.catch { error in
print("---Login Error---: \(error)")
}
上述就是一個 Promise 如何為異步編程中回調(diào)地獄提供的幫助例子,可讀性和可維護性都變得很高。
概念
Promise 是解決例如在多個線程中并行執(zhí)行重操作,延遲執(zhí)行代碼這些難以調(diào)試、容易中斷的問題的解決方案之一。
Promise 可以有三種狀態(tài):
pending - 待定狀態(tài),Promise對象剛被初始化的狀態(tài)
fulfilled - 滿足狀態(tài)(成功情況,可進行例如更新UI操作)
rejected - 拒絕狀態(tài)(拋出錯誤)
一旦是 fulfilled 或 rejected 狀態(tài),Promise 不會再改變狀態(tài)
Promises 使用解析
Pending
let promise = Promise<String>.pending()
// ...
if success {
promise.fulfill("Hello world")
} else {
promise.reject(someError)
}
初始化 pending 狀態(tài)(暫時沒有異步操作),隨后根據(jù)狀態(tài)完成情況來更新 Promise 狀態(tài)。
Then
func work1(_ string: String) -> Promise<String> {
return Promise {
return string
}
}
func work2(_ string: String) -> Promise<Int> {
return Promise {
return Int(string) ?? 0
}
}
func work3(_ number: Int) -> Int {
return number * number
}
work1("10").then { string in
return work2(string)
}.then { number in
return work3(number)
}.then { number in
print(number) // 100
}
用于鏈式連接函數(shù)組成的隊列,回調(diào)鏈中的函數(shù)根據(jù)上一個函數(shù)狀態(tài)情況依次被調(diào)用。
Catch
work1("abc").then { string in
return work2(string)
}.then { number in
return work3(number) // Never executed.
}.then { number in
print(number) // Never executed.
}.catch { error in
print("Cannot convert string to number: \(error)")
}
拋出錯誤,只要出現(xiàn)異常,忽略鏈中剩余的任何 then 塊,catch 可以防止在鏈中任何位置來處理錯誤。
All
// Promises of same type:
all(contacts.map { MyClient.getAvatarFor(contact: $0) }).then(updateAvatars)
// Promises of different types:
all(
MyClient.getLocationFor(contact: contact),
MyClient.getAvatarFor(contact: contact)
).then { location, avatar in
self.updateContact(location, avatar)
}
在一個塊中等待所有函數(shù)調(diào)用結(jié)束后更新狀態(tài)。例如一個頁面的UI使用到三個網(wǎng)絡請求的數(shù)據(jù),等待全部加載完成再進行刷新。
Always
getCurrentUserContactsAvatars().then { avatars in
self.update(avatars)
}.catch { error in
self.showErrorAlert(error)
}.always {
self.label.text = "All done."
}
不論 promise 結(jié)果如何,在這之后總是執(zhí)行某項任務。
Recover
getCurrentUserContactsAvatars().recover { error in
print("Fallback to default avatars due to error: \(error)")
return self.getDefaultsAvatars()
}.then { avatars in
self.update(avatars)
}
可以默認實現(xiàn)錯誤處理,并且不打斷接下來鏈塊操作。
Resolve
func newAsyncMethodReturningAPromise() -> Promise<Data> {
return resolve { handler in
MyClient.wrappedAsyncMethodWithTypical(completion: handler)
}
}
用于返回一個Promise,提供一種常見的、方便的方法。
Timeout
超時處理
Validate
getAuthToken().validate { !$0.isEmpty }.then(getData).catch { error in
print("Failed to get auth token: \(error))
}
validate 在不破壞鏈的情況下檢查返回結(jié)果,來判斷獲取的內(nèi)容是否可靠再進行接下來的操作。
When
when(
MyClient.getLocationFor(contact: contact),
MyClient.getAvatarFor(contact: contact)
).then { location, avatar in
if let location = location.value, let avatar = avatar.value {
self.updateContact(location, avatar)
} else { // Optionally handle errors if needed.
if let locationError = location.error {
self.showErrorAlert(locationError)
}
if let avatarError = avatar.error {
self.showErrorAlert(avatarError)
}
}
}
when 與 all 類似,不同的是如果需要處理錯誤細節(jié),使用when會顯得更加方便。
參考:
Under the hood of Futures & Promises in Swift
google/promises/index.md