本文使用的 Promises 是谷歌最近開(kāi)源的輕量贤徒,高性能昼弟,安全属瓣,測(cè)試完備的 Promise 框架员舵。https://github.com/google/promises
Swift 關(guān)于 Promise 的優(yōu)秀框架還有 PromiseKit 和 BrightFutures。
什么是Futures & Promises?
Future
和 Promise
其實(shí)是一個(gè)東西捉兴。很多iOS開(kāi)發(fā)者可能沒(méi)有聽(tīng)說(shuō)過(guò) Promise屯阀。Promise 在 JavaScript
中最為活躍。JavaScript 中大部分代碼都是單線程的轴术,在沒(méi)有引入 Promise 時(shí)难衰,開(kāi)發(fā)者一般編寫(xiě)回調(diào)函數(shù),但在某些特殊的情況下回調(diào)嵌套非常的多逗栽,代碼就會(huì)變得非常難讀盖袭,難以調(diào)試。在 iOS 中彼宠,Swift
閉包 closure
反復(fù)嵌套閉包(OC 代碼塊 block 嵌套代碼塊)的時(shí)候問(wèn)題也同樣嚴(yán)重鳄虱。通俗的說(shuō) Promise 就是鏈的方式對(duì)結(jié)果類型閉包的封裝,避免層層閉包重復(fù)嵌套難以閱讀凭峡。
簡(jiǎn)單場(chǎng)景
日常開(kāi)發(fā)中可能會(huì)使用這樣的網(wǎng)絡(luò)請(qǐng)求:
- 第三方平臺(tái)授權(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)")
}
}
可以看到即便是將請(qǐng)求方法剝離拙已,使用枚舉enum封裝了網(wǎng)絡(luò)請(qǐng)求回調(diào),代碼還是一層套一層摧冀,顯然是陷入了回調(diào)地獄倍踪。
在使用 Promise 后,代碼看起來(lái)像是這樣
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)")
}
上述就是一個(gè) Promise 如何為異步編程中回調(diào)地獄提供的幫助例子索昂,可讀性和可維護(hù)性都變得很高建车。
概念
Promise 是解決例如在多個(gè)線程中并行執(zhí)行重操作,延遲執(zhí)行代碼這些難以調(diào)試椒惨、容易中斷的問(wèn)題的解決方案之一缤至。
Promise 可以有三種狀態(tài):
pending - 待定狀態(tài),Promise對(duì)象剛被初始化的狀態(tài)
fulfilled - 滿足狀態(tài)(成功情況康谆,可進(jìn)行例如更新UI操作)
rejected - 拒絕狀態(tài)(拋出錯(cuò)誤)
一旦是 fulfilled 或 rejected 狀態(tài)领斥,Promise 不會(huì)再改變狀態(tài)
Promises 使用解析
Pending
let promise = Promise<String>.pending()
// ...
if success {
promise.fulfill("Hello world")
} else {
promise.reject(someError)
}
初始化 pending 狀態(tài)(暫時(shí)沒(méi)有異步操作),隨后根據(jù)狀態(tài)完成情況來(lá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
}
用于鏈?zhǔn)竭B接函數(shù)組成的隊(duì)列月洛,回調(diào)鏈中的函數(shù)根據(jù)上一個(gè)函數(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)")
}
拋出錯(cuò)誤描睦,只要出現(xiàn)異常膊存,忽略鏈中剩余的任何 then 塊导而,catch 可以防止在鏈中任何位置來(lái)處理錯(cuò)誤忱叭。
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)
}
在一個(gè)塊中等待所有函數(shù)調(diào)用結(jié)束后更新?tīng)顟B(tài)隔崎。例如一個(gè)頁(yè)面的UI使用到三個(gè)網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù),等待全部加載完成再進(jìn)行刷新韵丑。
Always
getCurrentUserContactsAvatars().then { avatars in
self.update(avatars)
}.catch { error in
self.showErrorAlert(error)
}.always {
self.label.text = "All done."
}
不論 promise 結(jié)果如何疙描,在這之后總是執(zhí)行某項(xiàng)任務(wù)堪唐。
Recover
getCurrentUserContactsAvatars().recover { error in
print("Fallback to default avatars due to error: \(error)")
return self.getDefaultsAvatars()
}.then { avatars in
self.update(avatars)
}
可以默認(rèn)實(shí)現(xiàn)錯(cuò)誤處理,并且不打斷接下來(lái)鏈塊操作。
Resolve
func newAsyncMethodReturningAPromise() -> Promise<Data> {
return resolve { handler in
MyClient.wrappedAsyncMethodWithTypical(completion: handler)
}
}
用于返回一個(gè)Promise丝蹭,提供一種常見(jiàn)的、方便的方法惨撇。
Timeout
超時(shí)處理
Validate
getAuthToken().validate { !$0.isEmpty }.then(getData).catch { error in
print("Failed to get auth token: \(error))
}
validate 在不破壞鏈的情況下檢查返回結(jié)果虐秦,來(lái)判斷獲取的內(nèi)容是否可靠再進(jìn)行接下來(lá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 類似碗短,不同的是如果需要處理錯(cuò)誤細(xì)節(jié)受葛,使用when會(huì)顯得更加方便。
參考:
Under the hood of Futures & Promises in Swift
google/promises/index.md