PromiseKit 常見問題

全部文章

以下是對 PromiseKitREADME.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 }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容