全部文章
以下是對
PromiseKit的README.md的翻譯。
為什么我應(yīng)該使用 PromiseKit (X-Promises-Foo)?
- PromiseKit 非常注重開發(fā)人員的體驗(yàn)。作為開發(fā)者,如果你關(guān)系自己的編程體驗(yàn),那就用 PromiseKit 吧。
- 你是否想修復(fù)所有的 bug?那就用 PromiseKit 吧。
- 你是否想提高編程和獲取結(jié)果的速度?那就用 PromiseKit 吧。
- 你是否想要一個持續(xù)不斷積極維護(hù)了 6 年的庫?那就用 PromiseKit 吧。
- 你是否想要一個被社區(qū)公認(rèn)為最好的提供 Promises/Futures 功能的庫?那就用 PromiseKit 吧。
- 你是想直接在蘋果的 SDK 上使用 Promise,而不用自己去實(shí)現(xiàn)相關(guān)的功能?那就用 PromiseKit 吧。
- 你是否想在 Swift 3.x, Swift 4.x, ObjC, iOS, tvOS, watchOS, macOS, Android & Linux 上使用 Promise?那就用 PromiseKit 吧。
- 為了保證 PromiseKit 的正確性,整個框架都通過測試用例進(jìn)行了測試 Promises/A+ test suite。
怎樣創(chuàng)建一個狀態(tài)為 fulfilled 的 Void 的 promise?
let foo = Promise()
// or:
let bar = Promise.value(())
如何快速 return?
// viod 類型
func foo() -> Promise<Void> {
guard thingy else {
return Promise()
}
//…
}
// 非 viod 類型
func bar() -> Promise<SomethingNotVoid> {
guard thingy else {
return .value(instanceOfSomethingNotVoid)
}
//…
}
是否需要關(guān)心循環(huán)引用?
通常情況下,不需要。Promise 在完成后,將會釋放所有的處理程序,因此指向 self 的引用也將會被釋放。
但是,如果你不想在調(diào)用鏈中包含任何副作用,那你依然需要使用 weak self(并檢查 self == nil),來避免任何副作用的產(chǎn)生。
根據(jù)我們的經(jīng)驗(yàn),很多開發(fā)人員認(rèn)為應(yīng)該避免的副作用,實(shí)際上并不是副作用。
副作用包括修改應(yīng)用程序的全局狀態(tài),而不包括修改 view-controller 的顯示狀態(tài)。所以,應(yīng)該避免設(shè)置 UserDefaults 和修改應(yīng)用程序的數(shù)據(jù)庫,至于修改一個 UILabel 的 text 則不用擔(dān)心。
關(guān)于這個話題,可以查看 This StackOverflow question 中的討論。
是否需要持有 promise 的實(shí)例?
不需要。在處理程序執(zhí)行結(jié)束之前,promise 中的處理程序會持有這個 promise。當(dāng)所有的處理程序執(zhí)行完畢時(shí),這個 promise 就會被釋放。所以,當(dāng)你需要獲取調(diào)用鏈完成之后的結(jié)果值時(shí),你才需要持有這個 Promise。
應(yīng)該在哪里寫 catch?
catch 會終止調(diào)用鏈。所以你應(yīng)該盡可能的將 catch 放在 Promise 的尾部。一個典型的場景是,在 view Controller 中你可以在 catch 中向用戶展示一條提示信息。
也就是說,你應(yīng)該在多個 then 后面只寫一個 catch,并且返回的 promise 的內(nèi)部不包含 catch 處理程序。
很明顯這是一條建議,需要根據(jù)實(shí)際情況而定。
調(diào)用鏈的分支是如何運(yùn)行的?
假設(shè)有這樣一個的 promise:
let promise = foo()
然后調(diào)用了兩次 then:
promise.then {
// branch A
}
promise.then {
// branch B
}
這樣就有了一個分支調(diào)用鏈。當(dāng) promise 完成時(shí),兩個調(diào)用鏈將同時(shí)獲取到它的結(jié)果。但是,這個兩個鏈?zhǔn)峭耆珠_的,Swift 也會提示你給這兩個鏈都加上 catch 處理程序。
你可能會想省略掉其中的一個 catch,但是一定要小心,因?yàn)檫@樣做的話,swift 就無法提示你調(diào)用鏈必須有錯誤處理程序了。
promise.then {
// branch A
}.catch { error in
//…
}
_ = promise.then {
print("foo")
// ignoring errors here as print cannot error and we handle errors above
}
更安全的做法應(yīng)該是將兩個分支合并進(jìn)一個調(diào)用鏈中:
let p1 = promise.then {
// branch A
}
let p2 = promise.then {
// branch B
}
when(fulfilled: p1, p2).catch { error in
//…
}
值得注意的是:你也可以在一個 Promise 后添加多個 catch 處理程序。而且,如果調(diào)用鏈變?yōu)?rejected 時(shí),這些 catch 都會被調(diào)用。
PromiseKit 程序包大么?
不大。PromiseKit 包含的源代碼非常的少。實(shí)際上,它非常的輕量。和其他實(shí)現(xiàn) promise 的框架相比,會多出 6 年來修改 bug 和調(diào)整所造成的代碼。我們有出色的 Object-C 到 Swift 的橋接,并且考慮了很多業(yè)余項(xiàng)目不會考慮到的注意點(diǎn)。
為什么調(diào)試如此困難?
因?yàn)?Promise 是通過派發(fā)執(zhí)行的,所以在發(fā)生錯誤的時(shí),和平時(shí)使用跟蹤路徑的實(shí)現(xiàn)方式相比, 看到的堆棧信息要少一些。
一個解決方案是,在 debuging 的時(shí)候關(guān)閉派發(fā):
// Swift
DispatchQueue.default = zalgo
//ObjC
PMKSetDefaultDispatchQueue(zalgo)
不要一直這樣寫。在正常的使用中,我們會始終會用派發(fā)的方式來避免這個常見的錯誤??梢圆榭催@篇博客。
怎么沒有 all()?
一些 Promise 框架提供了 all 來等待多個 Promise 的結(jié)果。我們提供的方法是 when,他們本質(zhì)上是一樣的。我們選擇使用 when 是因?yàn)樗歉R姷男g(shù)語,而且在代碼當(dāng)中更易閱讀
應(yīng)該如何測試 Promise 返回的 API?
可以使用 XCTestExpectation 進(jìn)行測試。
我們也提供了 wait() 和 hang()。如果一定要使用這些方法,那么你一定要小心一點(diǎn),因?yàn)檫@些方法會阻止當(dāng)前線程。
PromiseKit 是線程安全的么?
是的,全部都是。
但是,你在 then 中添加的代碼可能不是線程安全的。
只要確保不要在并發(fā)隊(duì)列中訪問除了調(diào)用鏈之外的狀態(tài)就可以了。PromiseKit 的處理程序默認(rèn)運(yùn)行在 main 隊(duì)列,這個隊(duì)列又是串行的,所以通常你不用考慮這個問題。
Object-C 和 Swift 中為什么使用不同的類?
Promise<T> 是通用的,然而無法用 Object-C 進(jìn)行表示。
PromiseKit 是否符合 Promise/A+?
是的??梢酝ㄟ^我們的測試程序查看這一點(diǎn)。
PromiseKit 和 RxSwift/ReactiveSwift 有什么差別?
PromiseKit 要簡單很多。
PromiseKit 和 RxSwift 最大的差別是:RxSwift 中的 Observables(相當(dāng)于 PromiseKit 當(dāng)中的 Promise)不一定返回一個結(jié)果,他可能返回 0 個,一個或者無限個值流。這個概念上的細(xì)微差別,導(dǎo)致 API 即強(qiáng)大又復(fù)雜。
RxSwift 致力于編程方式上的轉(zhuǎn)變。它建議將代碼重構(gòu)為值的傳遞。在恰當(dāng)?shù)牡胤绞褂?RxSwift,會大大的簡化代碼。但是,并非所有的應(yīng)用程序都適合使用 RxSwift。
相比之下,PromiseKit 選擇性的將響應(yīng)式編程中最難的部分應(yīng)用于純 swift 開發(fā),即異步管理部分。它是一個更加通用的工具。只需要將異步代碼轉(zhuǎn)化為使用 Promise,就可以使代碼的更加的簡潔、簡單,而且健壯。(這個轉(zhuǎn)化過程又非常的容易。)
對開發(fā)人員來說,用 Promises 寫出的代碼更加的清晰。RxSwift 則不然。去看看使用 RxSwift 實(shí)現(xiàn)的注冊功能,然后你就會發(fā)現(xiàn)我說的是不是真的。(注意:這可是 RxSwift 自己的示例程序)
即使 PromiseKit 和 RxSwift 看起來相似,但是實(shí)現(xiàn)方式存在很多差異:
- RxSwift 使用一個特殊的元素(subscribers)來終止調(diào)用鏈。而 PromiseKit 中,鏈中的所有元素都使用了相同的代碼模式。
- 在 RxSwift 的 API 中定義了非常多的操作符,然后為每個操作符提供相應(yīng)的實(shí)現(xiàn)。PromiseKit 僅提供了一些實(shí)用的方法來幫助你解決特定的情況,編寫調(diào)用鏈非常的簡單,同時(shí)又不需要在庫中添加額外的代碼。
- PromiseKit 會派發(fā)所有的處理程序。RxSwift 僅在指定時(shí)才派發(fā)。此外,在 PromiseKit 中,調(diào)度狀態(tài)是鏈的屬性,而不是處理程序的。RxSwift 功能更強(qiáng)大,但是復(fù)雜。PromiseKit 簡單,可預(yù)測,并且安全。
- 在 PromiseKit 中,分支鏈都會引用共享的父類。在 RxSwift 中,分支會重新創(chuàng)建一個并行鏈,然后在鏈的開頭重新運(yùn)行代碼,也有可能不是這樣。確定代碼的執(zhí)行規(guī)則非常的復(fù)雜。由其他代碼創(chuàng)建的調(diào)用鏈,你根本無法判斷出它的執(zhí)行規(guī)則。
- 因?yàn)?RxSwift 鏈不一定會自己終止,所有 RxSwift 需要你負(fù)責(zé)一些垃圾回收的功能。所有的 promises 只會產(chǎn)生一個單一的值,執(zhí)行結(jié)束的時(shí)候回自動銷毀。
更多信息,可以查看這個話題。
Promise 什么時(shí)候開始執(zhí)行?
時(shí)常有人困惑 Promise 是什么時(shí)候開始執(zhí)行的?是立即執(zhí)行?還是稍后?還是當(dāng)調(diào)用 then 時(shí)?
答案是:Promise 的內(nèi)容會在初始化的時(shí)候執(zhí)行,而且是在當(dāng)前線程上。舉例來說,在創(chuàng)建 Promise 之后,就會立即在控制臺打?。?Executing the promise body" ,而不用等到調(diào)用在 Promise 上 then。
let testPromise = Promise<Bool> {
print("Executing the promise body.")
return $0.fulfill(true)
}
定義在 Promise 內(nèi)的異步任務(wù)是怎樣的呢?他們行為和沒有使用 PromiseKit 時(shí)是一樣的。一個簡單的例子:
let testPromise = Promise<Bool> { seal in
print("Executing the promise body.")
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
print("Executing asyncAfter.")
return seal.fulfill(true)
}
}
"Executing the promise body." 信息會被立即打印,而 "Executing asyncAfter." 信息只會在 3 秒之后打印。這種情況下,DispatchQueue 決定了何時(shí)執(zhí)行交給它的任務(wù),和 PromiseKit 沒有關(guān)系。
如何將 Firebase 與 PromiseKit 結(jié)合在一起使用?
沒有將 Firebase 和 PromiseKit 一起使用的好方法。有關(guān)更詳細(xì)的解釋,請查看下一個問題
目前最好的方式是將調(diào)用鏈嵌入到 Firebase 的處理方法中:
foo.observe(.value) { snapshot in
firstly {
bar(with: snapshot)
}.then {
baz()
}.then {
baffle()
}.catch {
//…
}
}
我需要 then 執(zhí)行多次
這種情況下,不應(yīng)該使用 PromiseKit。Promise 只執(zhí)行一次。這是 Promise 的一個特性,而且這個特性為你的調(diào)用鏈提供了一個確定的預(yù)期。
如何修改執(zhí)行處理程序的默認(rèn)隊(duì)列
你可以修改 PromiseKit.conf.Q 中的值。這里有兩個變量來控制兩種處理程序的默認(rèn)隊(duì)列。一種典型的模式是:將所有的處理程序放到后臺隊(duì)列上執(zhí)行,而 finalizer 放在主隊(duì)列上執(zhí)行:
PromiseKit.conf.Q.map = .global()
PromiseKit.conf.Q.return = .main //NOTE this is the default
將這兩個隊(duì)里中的任何一個設(shè)置為 nil 時(shí)都要非常小心。設(shè)置會立即生效,在應(yīng)用中這通常不符合你的預(yù)期。但是,在運(yùn)行 specs 時(shí),想要立即運(yùn)行 promise 時(shí)非常有用。(這和 HTTP 請求中的 stubbing 思路是一樣的。)
// in your test suite setup code
PromiseKit.conf.Q.map = nil
PromiseKit.conf.Q.return = nil
如何在服務(wù)器上使用 PromiseKit?
如果你的服務(wù)器框架要求不能使用 main 隊(duì)列(例如:Kitura),就必須設(shè)置 PromiseKit 在默認(rèn)的情況下不能派發(fā)到 main 隊(duì)列。只要用下面的設(shè)置就行了:
PromiseKit.conf.Q = (map: DispatchQueue.global(), return: DispatchQueue.global())
注意:我們建議你使用自己的隊(duì)列,而不是 .global(),以此來提高性能。
下面是完成的例子:
import Foundation
import HeliumLogger
import Kitura
import LoggerAPI
import PromiseKit
HeliumLogger.use(.info)
let pmkQ = DispatchQueue(label: "pmkQ", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem)
PromiseKit.conf.Q = (map: pmkQ, return: pmkQ)
let router = Router()
router.get("/") { _, response, next in
Log.info("Request received")
after(seconds: 1.0).done {
Log.info("Sending response")
response.send("OK")
next()
}
}
Log.info("Starting server")
Kitura.addHTTPServer(onPort: 8888, with: router)
Kitura.run()
如何設(shè)置控制臺的輸出?
默認(rèn)情況下,PromiseKit 在某些事件下會在控制臺輸出消息。比如下面的事件:
- 一個 promise 或者 guarantee 阻塞了主線程
- 一個 promise 還沒有完成就被釋放了
- 在 promise 的執(zhí)行過程中發(fā)生的錯誤沒有被妥善處理
如果要關(guān)閉或者重定向這個輸出過程,你可以在執(zhí)行所有的 promise 之前,通過給 PMKConfiguration 一個線程安全的閉包來修改。
conf.logHandler = { event in }