獨立博客 ZYF.IMhtml
在 GitHub Trending 中老是看到 mxcl/PromiseKit 它是主要解決的是 「回調地獄」 的問題,決定嘗試用一下。git
環境:Swift 4.二、PromiseKit 6github
下面是一個典型的 promise 鏈式(chain)調用:編程
firstly {
login()
}.then { creds in
fetch(avatar: creds.user)
}.done { image in
self.imageView = image
}
複製代碼
若是這段代碼使用完成回調(completion handler
)實現,他將是:json
login { creds, error in
if let creds = creds {
fetch(avatar: creds.user) { image, error in
if let image = image {
self.imageView = image
}
}
}
}
複製代碼
then
是完成回調的另外一種方式,可是它更豐富。在處級階段的理解,它更具備可讀性。上面的 promise chain 更容易閱讀和理解:一個異步操做接着另外一個,一行接一行。它與程序代碼很是接近,由於咱們很容易獲得 Swift 的當前狀態。swift
done
與 then
基本是同樣的,可是它將再也不返回 promise。它是典型的在末尾 「success」 部分的 chain。在上面的例子 done
中,咱們接收到了最終的圖片並使用它設置了 UI。數組
讓咱們對比一下兩個 login
的方法簽名:promise
// Promise:
func login() -> Promise<Creds>
// Compared with:
func login(completion: (Creds?, Error?) -> Void)
// 可選型,二者都是可選
複製代碼
區別在於 promise,方法返回 promises 而不是的接受和運行回調。每個處理器(handler)都會返回一個 promise。Promise 對象們定義 then
方法,該方法在繼續鏈式調用以前等待 promise 的完成。chains 在程序上解決,一次一個 promise。bash
Promise 表明將來異步方法的輸入值。它有一個表示它包裝的對象類型的類型。例如,在上面的例子裏,login
的返回 Promise 值表明一個 Creds 的一個實例。閉包
能夠注意到這與 completion pattern 的不一樣,promises chain 彷佛忽略錯誤。並非這樣,實際上:promise chain 使錯誤處理更容易訪問(accessible),並使錯誤更難被忽略。
有了 promises,錯誤在 promise chain 上級聯(cascade along),確保你的應用的健壯(robust)和清晰的代碼。
firstly {
login()
}.then { creds in
fetch(avatar: creds.user)
}.done { image in
self.imageView = image
}.catch {
// 整個 chain 上的錯誤都到了這裏
}
複製代碼
若是你忘記了 catch 這個 chain,Swift 會發出警告
每一個 promise 都是一個表示單個(individual)異步任務的對象。若是任務失敗,它的 promise 將成爲 rejected
。產生 rejected
promises 將跳事後面全部的 then
,而是將執行 catch
。(嚴格上說是執行後續全部的 catch
處理)
這與 completion handler 對比:
func handle(error: Error) {
//...
}
login { creds, error in
guard let creds = creds else { return handle(error: error!) }
fetch(avatar: creds.user) { image, error in
guard let image = image else { return handle(error: error!) }
self.imageView.image = image
}
}
複製代碼
使用 guard
和合並錯誤對處理有所保證,可是 promise chain 更具備可讀性。
firstly {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
return login()
}.then {
fetch(avatar: $0.user)
}.done {
self.imageView = $0
}.ensure {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch {
// ...
}
複製代碼
不管在 chain 哪裏結束,成功或者失敗,ensure
終將被執行。也可使用 finally
來完成相同的事情,區別是沒有返回值。
spinner(visible: true)
firstly {
foo()
}.done {
// ...
}.catch {
// ...
}.finally {
self.spinner(visible: false)
}
複製代碼
多個異步操做同時處理時可能又難又慢。例如當 操做1
和 操做2
都完成時再返回結果:
// 串行操做
operation1 { result1 in
operation2 { result2 in
finish(result1, result2)
}
}
複製代碼
// 並行操做
var result1: ...!
var result2: ...!
let group = DispatchGroup()
group.enter()
group.enter()
operation1 {
result1 = $0
group.leave()
}
operation2 {
result2 = $0
group.leave()
}
group.notify(queue: .main) {
finish(result1, result2)
}
複製代碼
令人 Promises 將變得容易不少:
firstly {
when(fulfilled: operation1(), operation2())
}.done { result1, result2 in
// ...
}
複製代碼
when
等待全部的完成再返回 promises 結果。
PromiseKit 提過了一些 Apple API 的擴展,例如:
firstly {
CLLocationManager.promise()
}.then { location in
CLGeocoder.reverseGeocode(location)
}.done { placemarks in
self.placemark.text = "\(placemarks.first)"
}
複製代碼
同時須要指定 subspaces:
pod "PromiseKit"
pod "PromiseKit/CoreLocation"
pod "PromiseKit/MapKit"
複製代碼
更多的擴展能夠查詢 PromiseKit organization,甚至擴展了 Alamofire 這樣的公共庫。
有時你的 chains 仍然須要以本身開始,或許你使用的三方庫沒有提供 promises 或者本身寫了異步系統,不要緊,他們很是容易添加 promises。若是你查看了 PromiseKit 的標準擴展,能夠看到使用了下面相同的描述:
已有代碼:
func fetch(completion: (String?, Error?) -> Void)
複製代碼
轉換:
func fetch() -> Promise<String> {
return Promise { fetch(completion: $0.resolve) }
}
複製代碼
更具備可讀性的:
func fetch() -> Promise<String> {
return Promise { seal in
fetch { result, error in
seal.resolve(result, error)
}
}
}
複製代碼
Promise 初始化程序提供的 seal
對象定義了不少處理 garden-variety
完成回調的方法。
PromiseKit 設置嘗試以
Promise(fetch)
進行處理,可是完成經過編譯器的消歧義。
從 PromiseKit 5 開始,提供了 Guarantee 以作補充,目的是完善 Swift 強的的異常處理。
Guarantee
永遠不會失敗,因此不能被 rejected
。
firstly {
after(seconds: 0.1)
}.done {
// 這裏不要加 catch
}
複製代碼
Guarantee
的語法相較更簡單:
func fetch() -> Promise<String> {
return Guarantee { seal in
fetch { result in
seal(result)
}
}
}
// 減小爲
func fetch() -> Promise<String> {
return Guarantee(resolver: fetch)
}
複製代碼
then
要求返回另外一個 promisemap
要求返回一個 object 或 value 類型compactMap
要求返回一個 可選型,如過返回 nil
,chain 將失敗並報錯 PMKError.compactMap
firstly {
URLSession.shared.dataTask(.promise, with: rq)
}.compactMap {
try JSONSerialization.jsonObject($0.data) as? [String]
}.done { arrayOfStrings in
// ...
}.catch { error in
// Foundation.JSONError if JSON was badly formed
// PMKError.compactMap if JSON was of different type
}
複製代碼
除此以外還有:thenMap
compactMapValues
firstValue
etc
get
會獲得 done
中相同值。
firstly {
foo()
}.get { foo in
// ...
}.done { foo in
// same foo!
}
複製代碼
爲 debug 提供 tap
,與 get
相似可是能夠獲得 Result<T>
這樣就能夠檢查 chain 上的值:
firstly {
foo()
}.tap {
print($0)
}.done {
// ...
}.catch {
// ...
}
複製代碼
上面例子中的 firstly
是語法糖,非必須可是可讓 chains 更有可讀性。
firstly {
login()
}.then { creds in
// ...
}
// 也能夠
login().then { creds in
// ...
}
複製代碼
知識點:login()
返回了一個 Promise
,同時全部的 Promise
有一個 then
方法。firstly
返回一個 Promise
,一樣 then
也返回一個 Promise
。
when(fulfilled:)
在全部異步操做執行完後才執行回調,一個失敗 chain 將 rejects。It's important to note that all promises in the when continue. Promises have no control over the tasks they represent. Promises are just wrappers around tasks.when(resolved:)
使一個或多個組件承諾失敗也會等待。此變體 when
生成的值是 Result<T>
的數組,全部要保證相同的泛型。race
只要有一個異步操做執行完畢,就馬上執行 then
回調。其它沒有執行完畢的異步操做仍然會繼續執行,而不是中止。Swift 有自動推斷返回值和單行返回。
foo.then {
bar($0)
}
// is the same as:
foo.then { baz -> Promise<String> in
return bar(baz)
}
複製代碼
這樣有好有壞,具體能夠查詢 Troubleshooting
Reference:
-- EOF --