作者:Soroush Khanlou讥此,原文鏈接,原文日期:2016/8/8
譯者:Cwift;校對:Crystal Sun花吟;定稿:CMB
譯者注:英文原文發(fā)布時(shí)間較早,故原文代碼中的 Swift 版本較舊厨姚,但是作者已將 GitHub 上的 Promise 示例代碼更新到了最新 Swift 版本衅澈,所以譯者在翻譯本文時(shí),將文章里的代碼按照 GitHub 上的示例代碼進(jìn)行了替換谬墙,更新成了最新版本的 Swift 代碼今布。
上周,我寫了一篇介紹 Promise 的文章拭抬,Promise 是處理異步操作的高階模塊部默。只需要使用 fulfill()
、reject()
和 then()
等函數(shù)造虎,就可以簡單自由地構(gòu)建大量的功能傅蹂。本文會展示我在 Promise 方面的一些探索。
Promise.all
Promise.all
是其中的典型累奈,它保存所有異步回調(diào)的值贬派。這個(gè)靜態(tài)函數(shù)的作用是等待所有的 Promise 執(zhí)行 fulfill(履行) ,一旦全部執(zhí)行完畢澎媒,Promise.all
會使用所有履行后的值組成的數(shù)組對自己執(zhí)行 fulfill搞乏。例如,你可能想在代碼中對數(shù)組中的每個(gè)元素打點(diǎn)以捕獲某個(gè) API 的完成狀態(tài)戒努。使用 map
和 Promise.all
很容易實(shí)現(xiàn):
let userPromises = users.map({ user in
APIClient.followUser(user)
})
Promise.all(userPromises).then({
//所有的用戶都已經(jīng)執(zhí)行了 follow请敦!
}).catch({ error in
//其中一個(gè) API 失敗了镐躲。
})
要使用 Promise.all
,需要首先創(chuàng)建一個(gè)新的 Promise侍筛,它代表所有 Promise 的組合狀態(tài)萤皂,如果參數(shù)中的數(shù)組為空,可以立即執(zhí)行 fulfill匣椰。
public static func all<T>(_ promises: [Promise<T>]) -> Promise<[T]> {
return Promise<[T]>(work: { fulfill, reject in
guard !promises.isEmpty else { fulfill([]); return }
})
}
在這個(gè) Promise 內(nèi)部裆熙,遍歷每個(gè)子 Promise,并分別為它們添加成功和失敗的處理流程禽笑。一旦有子 Promise 執(zhí)行失敗了入录,就可以拒絕高階的 Promise。
for promise in promises {
promise.then({ value in
}).catch({ error in
reject(error)
})
}
只有當(dāng)所有的 Promise 都執(zhí)行成功佳镜,才可以 fulfill
高階的 Promise僚稿。檢查一下以確保沒有一個(gè) Promise 被拒絕或者掛起,使用一點(diǎn)點(diǎn) flatMap
的魔法蟀伸,就可以對 Promise 的組合執(zhí)行 fulfill 操作了蚀同。完整的方法如下:
public static func all<T>(_ promises: [Promise<T>]) -> Promise<[T]> {
return Promise<[T]>(work: { fulfill, reject in
guard !promises.isEmpty else { fulfill([]); return }
for promise in promises {
promise.then({ value in
if !promises.contains(where: { $0.isRejected || $0.isPending }) {
fulfill(promises.flatMap({ $0.value }))
}
}).catch({ error in
reject(error)
})
}
})
}
請注意,Promise 只能履行或者拒絕一次啊掏。如果第二次調(diào)用 fulfill
或者 reject
蠢络,不會對 Promise 的狀態(tài)造成任何影響。
因?yàn)?Promise 是狀態(tài)機(jī)脖律,它保存了與完成度有關(guān)的重要狀態(tài)谢肾。它是一種不同于 NSOperation
的方法。雖然 NSOperation
擁有一個(gè)完成回調(diào)以及操作的狀態(tài)小泉,但它不能保存得到的值芦疏,你需要自己去管理。
NSOperation
還持有線程模型以及優(yōu)先級順序相關(guān)的數(shù)據(jù)微姊,而 Promise 對代碼 如何 完成不做任何保證酸茴,只設(shè)置 完成后 需要執(zhí)行的代碼。Promise 類的定義足以證明兢交。它唯一的實(shí)例變量是 state
薪捍,狀態(tài)包括掛起、履行或者拒絕(以及對應(yīng)的數(shù)據(jù))配喳,此外還有一個(gè)回調(diào)數(shù)組酪穿。(它還包含了一個(gè)隔離隊(duì)列,但那不是真正的狀態(tài)晴裹。)
delay
有一種很有用的 Promise 可以延遲執(zhí)行自己的操作被济。
public static func delay(_ delay: TimeInterval) -> Promise<()> {
return Promise<()>(work: { fulfill, reject in
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: {
fulfill(())
})
})
}
在方法內(nèi)部,可以使用 usleep
或者其他方法來實(shí)現(xiàn)延遲涧团,不過 asyncAfter
方法足夠簡單只磷。當(dāng)構(gòu)建其他有趣的 Promise 時(shí)经磅,這個(gè)延遲 Promise 會很有用。
timeout
接下來钮追,使用 delay
來構(gòu)建 timeout
预厌。該 Promise 如果超過一定時(shí)間就會被拒絕。
public static func timeout<T>(_ timeout: TimeInterval) -> Promise<T> {
return Promise<T>(work: { fulfill, reject in
delay(timeout).then({ _ in
reject(NSError(domain: "com.khanlou.Promise", code: -1111, userInfo: [ NSLocalizedDescriptionKey: "Timed out" ]))
})
})
}
這個(gè) Promise 自身沒有太多用處元媚,但它可以幫助我們構(gòu)建一些其他功能的 Promise轧叽。
race
Promise.race
是 Promise.all
的小伙伴,它不需要等待所有的子 Promise 完成惠毁,它只履行或者拒絕第一個(gè)完成的 Promise犹芹。
public static func race<T>(_ promises: [Promise<T>]) -> Promise<T> {
return Promise<T>(work: { fulfill, reject in
guard !promises.isEmpty else { fatalError() }
for promise in promises {
promise.then(fulfill, reject)
}
})
}
因?yàn)?Promise 只能被執(zhí)行或拒絕一次,所以當(dāng)移除了 .pending
的狀態(tài)后鞠绰,在外部對 Promise 調(diào)用 fulfill
或者 reject
不會產(chǎn)生任何影響。
有了這個(gè)函數(shù)飒焦,使用 timeout
和 Promise.race
可以創(chuàng)建一個(gè)新的 Promise蜈膨,針對成功、失敗或者超過了規(guī)定時(shí)間三種情況牺荠。把它定義在 Promise
的擴(kuò)展中翁巍。
public func addTimeout(_ timeout: TimeInterval) -> Promise<Value> {
return Promise.race(Array([self, Promise<Value>.timeout(timeout)]))
}
可以在正常的 Promise 鏈中使用它,像下面這樣:
APIClient
.getUsers()
.addTimeout(0.5)
.then({
//在 0.5 秒內(nèi)獲取了用戶數(shù)據(jù)
})
.catch({ error in
//也許是超時(shí)引發(fā)的錯(cuò)誤休雌,也許是網(wǎng)絡(luò)錯(cuò)誤
})
這是我喜歡 Promise 的原因之一灶壶,它們的可組合性使得我們可以輕松地創(chuàng)建各種行為。通常需要保證 Promise 在 某個(gè)時(shí)刻 被履行或者拒絕杈曲,但是 timeout 函數(shù)允許我們用常規(guī)的方式來修正這種行為驰凛。
recover
recover
是另一個(gè)有用的函數(shù)。它可以捕獲一個(gè)錯(cuò)誤担扑,然后輕松地恢復(fù)狀態(tài)恰响,同時(shí)不會弄亂其余的 Promise 鏈。
我們很清楚這個(gè)函數(shù)的形式:它應(yīng)該接受一個(gè)函數(shù)涌献,該函數(shù)中接受錯(cuò)誤并返回新的 Promise胚宦。recover 方法也應(yīng)該返回一個(gè) Promise 以便繼續(xù)鏈接 Promise 鏈。
extension Promise {
public func recover(_ recovery: @escaping (Error) throws -> Promise<Value>) -> Promise<Value> {
}
}
在方法體中燕垃,需要返回一個(gè)新的 Promise枢劝,如果當(dāng)前的 Promise(self
)執(zhí)行成功,需要把成功狀態(tài)轉(zhuǎn)移給新的 Promise卜壕。
public func recover(_ recovery: @escaping (Error) throws -> Promise<Value>) -> Promise<Value> {
return Promise(work: { fulfill, reject in
self.then(fulfill).catch({ error in
})
})
}
然而您旁,catch
是另一回事了。如果 Promise 執(zhí)行失敗印叁,應(yīng)該調(diào)用提供的 recovery
函數(shù)被冒。該函數(shù)會返回一個(gè)新的 Promise军掂。無論 recovery 中的 Promise 執(zhí)行成功與否,都要把結(jié)果返回給新的 Promise昨悼。
//...
do {
try recovery(error).then(fulfill, reject)
} catch (let error) {
reject(error)
}
//...
完整的方法如下:
public func recover(_ recovery: @escaping (Error) throws -> Promise<Value>) -> Promise<Value> {
return Promise(work: { fulfill, reject in
self.then(fulfill).catch({ error in
do {
try recovery(error).then(fulfill, reject)
} catch (let error) {
reject(error)
}
})
})
}
有了這個(gè)新的函數(shù)就可以從錯(cuò)誤中恢復(fù)蝗锥。例如,如果網(wǎng)絡(luò)沒有加載我們期望的數(shù)據(jù)率触,可以從緩存中加載數(shù)據(jù):
APIClient.getUsers()
.recover({ error in
return cache.getUsers()
}).then({ user in
//更新 UI
}).catch({ error in
//錯(cuò)誤處理
})
retry
重試是我們可以添加的另一個(gè)功能终议。若要重試,需要指定重試的次數(shù)以及一個(gè)能夠創(chuàng)建 Promise 的函數(shù)葱蝗,該 Promise 包含了重試要執(zhí)行的操作(所以這個(gè) Promise 會被重復(fù)創(chuàng)建很多次)穴张。
public static func retry<T>(count: Int, delay: TimeInterval, generate: @escaping () -> Promise<T>) -> Promise<T> {
if count <= 0 {
return generate()
}
return Promise<T>(work: { fulfill, reject in
generate().recover({ error in
return self.delay(delay).then({
return retry(count: count-1, delay: delay, generate: generate)
})
}).then(fulfill).catch(reject)
})
}
- 如果數(shù)量不足 1,直接生成 Promise 并返回两曼。
- 否則皂甘,創(chuàng)建一個(gè)包含了需要重試的 Promise 的新的 Promise,如果失敗了悼凑,在
delay
時(shí)間之后恢復(fù)到之前的狀態(tài)并重試偿枕,不過此時(shí)的重試次數(shù)減為count - 1
。
基于之前編寫的 delay
和 recover
函數(shù)構(gòu)建了重試的函數(shù)户辫。
在上面的這些例子中渐夸,輕量且可組合的部分組合在一起,就得到了簡單優(yōu)雅的解決方案渔欢。所有的這些行為都是建立在 Promise 核心代碼所提供的簡單的 .then
和 catch
函數(shù)上的墓塌。通過格式化完成閉包的樣式,可以解決諸如超時(shí)奥额、恢復(fù)苫幢、重試以及其他可以通過簡單可重用的方式解決的問題。這些例子仍然需要一些測試和驗(yàn)證披坏,我會在未來一段時(shí)間內(nèi)慢慢地添加到 GitHub 倉庫 中态坦。
本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán)棒拂,最新文章請?jiān)L問 http://swift.gg伞梯。