主頁 > 移動端開發 > Swift之深入決議如何實作Promise

Swift之深入決議如何實作Promise

2021-10-25 07:18:07 移動端開發

一、前言

  • 異步編程是開發 App 的一個難點,多執行緒操作,執行緒的切換,多層嵌套 callback,使得代碼十分的混亂,難以理解,
func load(_ callback: (Value) -> Void) {
     loadService.load { result in
         let saveableItem = result.transfromToSaveableItem
         saveQueue.async {
              saveService.save(saveableItem) { saved in
                  let displayableItem = saved.tranfromToDisplayableItem
                   mainQueue.async {
                       callback(displayableItem)
                   }              
              }
         }
     }
}
  • Github 有很多直接或者間接能解決這個問題的方案,比如 RxSwift 等函式回應式編程框架,通過 observeOn,可以輕松的切換執行緒,并且使用 map,flatmap 等運算子鏈式的操作結果,可以讓我們輕松的寫出結構清晰的代碼,具體可以參考我的專欄:RAC+RxSwift,
  • Promise 是一種鏈接異步任務的方式,有三大要素:
    • 保存當前狀態的 State;
    • 保存回呼函式的 Callbacks;
    • 控制執行緒安全的方式,
  • 通常來說,異步任務會在異步操作完成時執行回呼閉包(有時候要準備兩個閉包,一個代表成功,一個代表失敗),要執行多個異步操作,必須將第二個異步操作放在第一個異步操作的完成閉包中執行:
APIClient.fetchCurrentUser(success: { currentUser in
	APIClient.fetchFollowers(user: currentUser, success: { followers in
		// 得到一個 followers 陣列
	}, failure: { error in
		// 錯誤處理
	})
}, failure: { error in
	// 錯誤處理
})
  • Promise 的作用是格式化完成閉包,簡化鏈式異步呼叫的形式,如果系統能夠分辨成功和失敗,那么組合這些異步操作就變得容易很多,比如,撰寫具有下列功能的可重用代碼:
    • 使用尾閉包執行一系列依賴關系的異步操作;
    • 通過一個完成閉包同時執行多個獨立的異步操作;
    • 多個異步操作競爭,回傳第一個完成的值;
    • 重試異步操作;
    • 為異步操作設定超時時間,
  • 如上代碼轉換為 Promise 樣式如下:
APIClient.fetchCurrentUser().then({ currentUser in
	return APIClient.fetchFollowers(user: currentUser)
}).then({ followers in
	// you now have an array of followers
)}.onFailure({ error in
	// hooray, a single failure block!
})
  • 可能你也注意到了,Promise 是將嵌套/縮進樣式的代碼變成一個層級的代碼:Promise 是一個 Monad,
  • Promise 在 JavaScript 社區中反響熱烈,因為 Node.js 的設計中包含了非常多異步操作,即便是簡單的任務也需要用到鏈式的異步回呼,即便只有三四個這樣的操作,代碼會變得笨重,Promise 終結了提心吊膽寫回呼的日子,Promise 已經寫進了 JavaScript ES6 的規范,JavaScript Promise 的運作機制請參考:JavaScript Promises … in Wicked Detail,
  • JavaScript Promise 實作的一個亮點是它有一個非常明確的規范,稱為 A+,具體詳情可以在 promisejs.org查看,這意味著依賴 JavaScript 的弱型別系統,多個 Promise 的實作可以融合,彼此之間可以互相操作,只要 Promise 中的 then 函式定義符合規范,它就可以和其他庫中的 Promise 連接,這實在是太棒了,
  • A+ 規范有一個非常好的 API,不使用 monad 中那個命名簡單且易于理解的 then 方法(在 A+ 規范中被重構為 flatMap 和 map),不過這個 API 不適合每個人,但我真的很喜歡它,并開始在 Swift 中實作一個類似的庫,你可以在 Github 的 Promise 上找個到這個庫,撰寫的程序很具有啟發性,

二、列舉

  • 大家都知道,列舉非常棒,因為 Promise 本質上是狀態機,所以列舉用在這里非常合適,JavaScript Promise 實作的參考如下所示:
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
  // 保存 PENDING, FULFILLED 或者 REJECTED 的狀態
  var state = PENDING;

  // 當出現 FULFILLED 或 REJECTED 狀態時保存值或者錯誤
var value = null;

  // 保存被 .then 或者 .done 函式觸發的成功 & 失敗的處理操作
  var handlers = [];
}
  • 應該是找不到比 Swift 的列舉實作更完美的例子了,如下,是 Swift 中的實作:
enum State<Value> {
    case pending
    case fulfilled(value: Value)
    case rejected(error: ErrorType)
}

final class Promise<Value> {    
    private var state: State<Value>
    private var callbacks: [Callback<Value>] = []
}
  • 外部資料依賴于 Promise 的具體狀態,所以被封裝到對應 case 的關聯值中,當 Promise 處于 .pending 狀態時,任何外部資料都沒有意義,列舉在型別系統中表達出的語意是不可思議的,唯一要批判的是泛型不能被嵌套進其它型別中,并且這個缺陷在 Swift 3 中不會更改,具體請參考:Nested generic type in function is not allowed,

三、型別系統

  • 創建一個新的 JavaScript Promise 時,可以使用便捷構造器:
var promise = new Promise(function(resolve, reject) {
	someAsyncRequest(function(error, result) {
		if (error) {
			reject(error);
		}
		resolve(result);
	});
});
  • 傳入了一個包含兩個其它函式的函式,主要有兩個功能:第一個函式引數對應 Promise 操作成功的情況,第二個對應了失敗的情況,這兩個函式的順序很重要,因為 JavaScript 不是型別安全的,如果在上面的第一行代碼中寫錯了順序,寫成了 reject, resolve,很容易就向 resolve 函式中引入了錯誤,另一方面,Swift 是型別安全的,這意味著 reject 函式的型別是 (ErrorType) -> Void),該函式不會被成功的結果所接受,所以再也不用擔心弄亂 reject 和 resolve 函式的順序,

四、太多的型別可能會令人沮喪

  • Promise 的型別中使用了泛型 Value,這是它的內部值型別,意味著可以通過型別推斷而不用指定具體的型別:
let promise = Promise(value: "initialValue") // a fulfilled Promise<String>
  • 因為 Promise 經常被鏈式呼叫,依靠型別推斷來確定型別將會特別有用,必須向鏈中的每個步驟添加明確的型別是件令人沮喪的事,最終的樣式也不是很有 Swift 的風格:
let promise = Promise<String, APIError>(value: "initialValue")
  • 這使得一行簡單的代碼增加了很多不必要的包袱,所以我洗掉了指定錯誤型別的功能,不幸的是,洗掉顯式的錯誤型別意味著不得不失去一個小小的型別系統的優勢,假設使用了一個叫 NoError 的空列舉,它有效地表達出 Promise 不能失敗的語意,因為空的列舉不能被初始化,所以 Promise 不能進入到 rejected 的狀態,這是一個令人心痛的損失,但我認為這是值得的,因為這樣在其它背景關系中使用 Promise 變得更簡單,希望能在實踐中使用這個類,以便深入體會并思考不設定錯誤型別否是個明智的決定,

五、函式式編程中的方法難以理解

  • Promise 的型別是一個 monad,也就是說可以對它呼叫 flatMap,傳遞給 flatMap 的函式會回傳一個新的 Promise,回傳的 Promise 的狀態將成為該呼叫鏈的狀態,
  • 不過,flatMap 的函式名是絕對不能忽視的,它無法使用一種易讀的方式表達函式中發生的事情,這是 A+ 規范的 Promise API 的優勢之一,JavaScript 中的 then 函式被多載為 flatMap 函式(為呼叫鏈回傳一個新的 Promise)和 map 函式(為呼叫鏈中的下一個 Promise 回傳一個新值),then 只意味著“下一步做這件事”,而不知道下一件事情的作業原理,

六、實作 Promise

① 第一個測驗

  • 先寫第一個測驗:
test(named: "0. Executor function is called immediately") { assert, done in
    var string: String = ""
    _ = Promise { string = "foo" }
    assert(string == "foo")
    done()
}
  • 通過此測驗,想實作傳遞一個函式給 Promise 的初始化函式,并立即呼叫此函式(沒有使用任何測驗框架,僅僅使用一個自定義的 test 方法,它在 Playground 中模擬斷言:PromisePlayground.swift),
  • 當我們運行 Playground,編譯器會報錯:
error: Promise.playground:41:9: error: use of unresolved identifier 'Promise'
    _ = Promise { string = "foo" }
        ^~~~~~~
  • 合理,因此需要定義 Promise 類:
class Promise {

}
  • 再運行,錯誤變為:
error: Promise.playground:44:17: error: argument passed to call that takes no arguments
    _ = Promise { string = "foo" }
                ^~~~~~~~~~~~~~~~~~
  • 必須定義一個初始化函式,它接受一個閉包作為引數,而且這個閉包應該被立即呼叫:
class Promise {
    init(executor: () -> Void) {
        executor()
    }
}
  • 由此,我們通過第一個測驗,目前還沒有寫出什么值得夸耀的東西,但耐心一點,實作將繼續增長:
Test 0. Executor function is called immediately passed 
  • 我們先將此測驗注釋掉,因為將來的 Promise 實作會變得有些不同,

② 最低限度

  • 第二個測驗如下:
test(named: "1.1 Resolution handler is called when promise is resolved sync") { assert, done in
    let string: String = "foo"
    let promise = Promise<String> { resolve in
        resolve(string)
    }
    promise.then { (value: String) in
        assert(string == value)
        done()
    }
}
  • 這個測驗挺簡單,但添加了一些新內容到 Promise 類,創建的這個 promise 有一個 resolution handler(即閉包的 resolve 引數),之后立即呼叫它(傳遞一個 value),然后使用 promise 的then方法來訪問 value 并用斷言確保其值,
  • 在開始實作之前,需要引入另外一個不太一樣的測驗:
test(named: "1.2 Resolution handler is called when promise is resolved async") { assert, done in
    let string: String = "foo"
    let promise = Promise<String> { resolve in
        after(0.1) {
            resolve(string)
        }
    }
    promise.then { (value: String) in
        assert(string == value)
        done()
    }
}
  • 不同于測驗 1.1 ,這里的 resove 方法被延遲呼叫,這意味著,在 then 里,value 不會立馬可用(因為 0.1 秒的延遲,呼叫 then 時,resolve 還未被呼叫),
  • 開始理解這里的“問題”,必須處理異步:
    • promise 是一個狀態機,當它被創建時,promise 處于pending狀態,一旦 resolve 方法被呼叫(與一個 value),promise 將轉到 resolved 狀態,并存盤這個 value,
    • then 方法可在任意時刻被呼叫,而不管 promise 的內部狀態(即不管 promise 是否已有一個 value),當這個 promise 處于 pending 狀態時,呼叫 then,value 將不可用,因此需要存盤此回呼,之后一旦 promise 變成 resolved,就能使用 resolved value 來觸發同樣的回呼,
  • 現在對要實作的東西有了更好的理解,那就先以修復編譯器的報錯開始:
error: Promise.playground:54:19: error: cannot specialize non-generic type 'Promise'
    let promise = Promise<String> { resolve in
                  ^      ~~~~~~~~
  • 必須給 Promise 型別添加泛型,誠然,一個 promise 是這樣的東西:它關聯著一個預定義的型別,并能在被解決時,將一個此型別的 value 保留住:
class Promise<Value> {

    init(executor: () -> Void) {
        executor()
    }
}
  • 現在錯誤為:
error: Promise.playground:54:37: error: contextual closure type '() -> Void' expects 0 arguments, but 1 was used in closure body
    let promise = Promise<String> { resolve in
                                    ^
  • 必須提供一個 resolve 函式傳遞給初始化函式(即 executor):
class Promise<Value> {
    init(executor: (_ resolve: (Value) -> Void) -> Void) {
        executor()
    }
}
  • 注意這個 resolve 引數是一個函式,它消耗一個 value:(Value) -> Void,一旦 value 被確定,這個函式將被外部世界呼叫,
    編譯器依然不開心,因為需要提供一個 resolve 函式給 executor,那就創建一個 private:
class Promise<Value> {
    init(executor: (_ resolve: @escaping (Value) -> Void) -> Void) {
        executor(resolve)
    }

    private func resolve(_ value: Value) -> Void {
        // To implement
        // This will be called by the outside world when a value is determined
    }
}
  • 當所有錯誤都被解決的時候,再來實作 resolve,下一個錯誤很簡單,方法 then 還未定義:
error: Promise.playground:61:5: error: value of type 'Promise<String>' has no member 'then'
    promise.then { (value: String) in
    ^~~~~~~ ~~~~
  • 現在修復它:
class Promise<Value> {

    init(executor: (_ resolve: @escaping (Value) -> Void) -> Void) {
        executor(resolve)
    }

    func then(onResolved: @escaping (Value) -> Void) {
        // To implement
    }

    private func resolve(_ value: Value) -> Void {
        // To implement
    }
}
  • 現在編譯通過,我們之前說過一個 Promise 就是一個狀態機,它有一個 pending 狀態和一個 resolved 狀態,可以使用 enum 來定義它們:
enum State<T> {
    case pending
    case resolved(T)
}
  • Swift 可以直接存盤 promise 的 value 在 enum 中,現在我要在 Promise 的實作中定義一個狀態,其默認值為 .pending,還需要一個私有函式,它能在當前還處于 .pending 狀態時更新狀態:
class Promise<Value> {

    enum State<T> {
        case pending
        case resolved(T)
    }

    private var state: State<Value> = .pending

    init(executor: (_ resolve: @escaping (Value) -> Void) -> Void) {
        executor(resolve)
    }

    func then(onResolved: @escaping (Value) -> Void) {
        // To implement
    }

    private func resolve(_ value: Value) -> Void {
        // To implement
    }

    private func updateState(to newState: State<Value>) {
        guard case .pending = state else { return }
        state = newState
    }
}
  • 注意 updateState(to:) 函式先檢查了當前處于 .pending 狀態,如果 promise 已經處于 .resolved 狀態,那它就不能再變成其它狀態,
    現在是時候在必要時更新 promise 的狀態,即當 resolve 函式被外部世界傳遞 value 呼叫時:
private func resolve(_ value: Value) -> Void {
    updateState(to: .resolved(value))
}
  • 現在只缺少 then 方法還未實作,必須存盤回呼,并在 promise 被解決時呼叫回呼,現在來實作:
class Promise<Value> {

    enum State<T> {
        case pending
        case resolved(T)
    }

    private var state: State<Value> = .pending
    // we store the callback as an instance variable
    private var callback: ((Value) -> Void)?

    init(executor: (_ resolve: @escaping (Value) -> Void) -> Void) {
        executor(resolve)
    }

    func then(onResolved: @escaping (Value) -> Void) {
        // store the callback in all cases
        callback = onResolved
        // and trigger it if needed
        triggerCallbackIfResolved()
    }

    private func resolve(_ value: Value) -> Void {
        updateState(to: .resolved(value))
    }

    private func updateState(to newState: State<Value>) {
        guard case .pending = state else { return }
        state = newState
        triggerCallbackIfResolved()
    }

    private func triggerCallbackIfResolved() {
        // the callback can be triggered only if we have a value,
        // meaning the promise is resolved
        guard case let .resolved(value) = state else { return }
        callback?(value)
        callback = nil
    }
}
  • 這里定義了一個實體變數 callback,以在 promise 處于.pending 狀態時保留回呼,同時,創建一個方法 triggerCallbackIfResolved,它先檢查狀態是否為 .resolved,然后傳遞拆包的 value 給回呼,這個方法在兩個地方被呼叫,一個是 then 方法中,如果 promise 已經在呼叫 then 時被解決;另一個在 updateState 方法中,因為那是 promise 更新其內部狀態從 .pending 到 .resolved 的地方,
  • 有了這些修改,測驗就成功通過:
Test 1.1 Resolution handler is called when promise is resolved sync passed (1 assertions)
Test 1.2 Resolution handler is called when promise is resolved async passed (1 assertions)
  • 但現在還需要做出一點改變,以得到一個真正的 Promise 實作,先來看看測驗:
test(named: "2.1 Promise supports many resolution handlers sync") { assert, done in
    let string: String = "foo"
    let promise = Promise<String> { resolve in
        resolve(string)
    }
    promise.then { value in
        assert(string == value)
    }
    promise.then { value in
        assert(string == value)
        done()
    }
}
test(named: "2.2 Promise supports many resolution handlers async") { assert, done in
    let string: String = "foo"
    let promise = Promise<String> { resolve in
        after(0.1) {
            resolve(string)
        }
    }
    promise.then { value in
        assert(string == value)
    }
    promise.then { value in
        assert(string == value)
        done()
    }
}
  • 這回對每個 promise 都呼叫了兩次 then,看看測驗輸出:
Test 2.1 Promise supports many resolution handlers sync passed (2 assertions)
Test 2.2 Promise supports many resolution handlers async passed (1 assertions)
  • 雖然測驗通過了,但你可能也注意問題,測驗 2.2 只有一個斷言,但應該是兩個,如果思考一下,這其實符合邏輯,在異步的測驗 2.2 中,當第一個 then 被呼叫時,promise 還處于 .pending 狀態,如之前所見,存盤了第一次 then 的回呼,但當第二次呼叫 then 時,promise 還是沒有被解決,依然處于 .pending 狀態,于是將回呼擦除換成了新的,只有第二個回呼會在將來被執行,第一個被忘記了,這使得測驗雖然通過,但只有一個斷言而不是兩個,
  • 解決辦法也很簡單,就是存盤一個回呼的陣列,并在 promise 被解決時觸發它們,更新一下:
class Promise<Value> {

    enum State<T> {
        case pending
        case resolved(T)
    }

    private var state: State<Value> = .pending
    // We now store an array instead of a single function
    private var callbacks: [(Value) -> Void] = []

    init(executor: (_ resolve: @escaping (Value) -> Void) -> Void) {
        executor(resolve)
    }

    func then(onResolved: @escaping (Value) -> Void) {
        callbacks.append(onResolved)
        triggerCallbacksIfResolved()
    }

    private func resolve(_ value: Value) -> Void {
        updateState(to: .resolved(value))
    }

    private func updateState(to newState: State<Value>) {
        guard case .pending = state else { return }
        state = newState
        triggerCallbacksIfResolved()
    }

    private func triggerCallbacksIfResolved() {
        guard case let .resolved(value) = state else { return }
        // We trigger all the callbacks
        callbacks.forEach { callback in callback(value) }
        callbacks.removeAll()
    }
}
  • 測驗通過,并且都有兩個斷言:
Test 2.1 Promise supports many resolution handlers sync passed (2 assertions)
Test 2.2 Promise supports many resolution handlers async passed (2 assertions)
  • 現在就已經創建了自己的 Promise 類,已經可以使用它來抽象異步邏輯,但它還有限制,
  • 如果從全域來看,我們知道 then 可以被重命名為 observe,它的目的是消費 promise 被解決后的 value,但它不回傳什么,這意味著暫時沒法串聯多個 promise,

③ 串聯多個 Promise

  • 如果不能串聯多個 promise,那 Promise 實作就不算完整,先來看看測驗,它將幫助我們實作這個特性:
test(named: "3. Resolution handlers can be chained") { assert, done in
    let string: String = "foo"
    let promise = Promise<String> { resolve in
        after(0.1) {
            resolve(string)
        }
    }
    promise
        .then { value in
            return Promise<String> { resolve in
                after(0.1) {
                    resolve(value + value)
                }
            }
        }
        .then { value in // the "observe" previously defined
            assert(string + string == value)
            done()
        }
}
  • 如測驗所見,第一個 then 創建了一個有新 value 的新 Promise 并回傳了它,第二個 then(前一節定義被稱為 observe)被串聯在后面,它訪問新的 value(其將是 “foofoo” ),但是很快在終端里看到錯誤:
error: Promise.playground:143:10: error: value of tuple type '()' has no member 'then'
        .then { value in
         ^
  • 我們必須創建一個 then 的多載,它接受一個能回傳 promise 的函式,為了能夠串聯呼叫 then,這個方法必須也回傳一個 promise,這個 then 的原型如下:
func then<NewValue>(onResolved: @escaping (Value) -> Promise<NewValue>) -> Promise<NewValue> {
    // to implement
}
  • 細心的你可能已經發現,在給 Promise 實作 flatMap,就如給 Optional 和 Array 定義 flatMap 一樣,也可以給 Promise 定義它,那么這個 “flatMap” 的 then 要怎么實作呢?
    • 需要回傳一個Promise?
    • 怎么給一個 promise?onResolved 方法?
    • onResolved 需要一個型別為 Value 的 value 為引數,該怎樣得到這個 value 呢?
  • 如果寫成代碼,大概如下:
func then<NewValue>(onResolved: @escaping (Value) -> Promise<NewValue>) -> Promise<NewValue> {
    then { value in // the "observe" one
        let promise = onResolved(value) // `promise` is a Promise<NewValue>
        // problem: how do we return `promise` to the outside ??
    }
    return // ??!!
}
  • 到這里就快好了,但還有個小問題需要修復:這個 promise 變數被傳遞給 then 的閉包所限制,我們不能將其作為函式的回傳值,我們要使用的技巧是創建一個包裝 Promise,它將執行我們目前所寫的代碼,然后在 promise 變數解決時被同時解決,換句話說,當 onResolved 方法提供的 promise 被解決并從外部得到一個值,那包裝的 promise 也就被解決并得到同樣的值,
  • 可能文字有些抽象,但如果寫成代碼,將看得更清楚:
func then<NewValue>(onResolved: @escaping (Value) -> Promise<NewValue>) -> Promise<NewValue> {
    // We have to return a promise, so let's return a new one
    return Promise<NewValue> { resolve in
        // this is called immediately as seen in test 0.
        then { value in // the "observe" one
            let promise = onResolved(value) // `promise` is a Promise<NewValue>
            // `promise` has the same type of the Promise wrapper
            // we can make the wrapper resolves when the `promise` resolves
            // and gets a value
            promise.then { value in resolve(value) }
        }
    }
}
  • 如果整理一下代碼,將有這樣兩個方法:
// observe
func then(onResolved: @escaping (Value) -> Void) {
    callbacks.append(onResolved)
    triggerCallbacksIfResolved()
}

// flatMap
func then<NewValue>(onResolved: @escaping (Value) -> Promise<NewValue>) -> Promise<NewValue> {
    return Promise<NewValue> { resolve in
        then { value in
            onResolved(value).then(onResolved: resolve)
        }
    }
}
  • 最后測驗通過:
Test 3. Resolution handlers can be chained passed (1 assertions)

④ 串聯多個 value

  • 如果能給某個型別實作 flatMap,那么就能利用 flatMap 為其實作 map,對于 Promise 來說,map 該是什么樣子?使用如下測驗:
test(named: "4. Chaining works with non promise return values") { assert, done in
    let string: String = "foo"
    let promise = Promise<String> { resolve in
        after(0.1) {
            resolve(string)
        }
    }
    promise
        .then { value -> String in
            return value + value
        }
        .then { value in // the "observe" then
            assert(string + string == value)
            done()
        }
}
  • 注意第一個 then 沒有再回傳一個 Promise,而是將其接收的值做了一個變換,這個新的 then 就對應于想添加的 map,
    編譯器報錯說必須實作此方法:
error: Promise.playground:174:26: error: declared closure result 'String' is incompatible with contextual type 'Void'
        .then { value -> String in
                         ^~~~~~
                         Void
  • 這個方法很接近 flatMap,唯一的不同是其引數 onResolved 函式回傳一個 NewValue,而不是 Promise,
// map
func then<NewValue>(onResolved: @escaping (Value) -> NewValue) -> Promise<NewValue> {
    // to implement
}
  • 之前說可以利用 flatMap 實作 map,在這里的情況,我們看到需要回傳一個 Promise,如果使用這個 “flatMap” 的 then,并創建一個 promise,再以映射后的 value 來直接解決,就可以搞定:
// map
func then<NewValue>(onResolved: @escaping (Value) -> NewValue) -> Promise<NewValue> {
    return then { value in // the "flatMap" defined before
        // must return a Promise<NewValue> here
        // this promise directly resolves with the mapped value
        return Promise<NewValue> { resolve in
            let newValue = onResolved(value)
            resolve(newValue)
        }
    }
}
  • 再一次,測驗通過:
Test 4. Chaining works with non promise return values passed (1 assertions)
  • 如果我們移除注釋,再看看做出了什么,有三個 then 方法的實作,能被使用或串聯:
// observe
func then(onResolved: @escaping (Value) -> Void) {
    callbacks.append(onResolved)
    triggerCallbacksIfResolved()
}

// flatMap
func then<NewValue>(onResolved: @escaping (Value) -> Promise<NewValue>) -> Promise<NewValue> {
    return Promise<NewValue> { resolve in
        then { value in
            onResolved(value).then(onResolved: resolve)
        }
    }
}

// map
func then<NewValue>(onResolved: @escaping (Value) -> NewValue) -> Promise<NewValue> {
    return then { value in
        return Promise<NewValue> { resolve in
            resolve(onResolved(value))
        }
    }
}

七、使用示例

  • 實作就完成,Promise 類已足夠完整來展示能夠用它做什么,假設 App 有一些用戶,結構如下:
struct User {
    let id: Int
    let name: String
}
  • 假設還有兩個方法,一個獲取用戶 id 串列,另一個使用 id 獲取某個用戶,而且假設想顯示第一個用戶的名字,
    通過實作,可以這樣做,使用之前定義的這三個 then:
func fetchIds() -> Promise<[Int]> {
    ...
}

func fetchUser(id: Int) -> Promise<User> {
    ...
}

fetchIds()
    .then { ids in // flatMap
        return fetchUser(id: ids[0])
    }
    .then { user in // map
        return user.name
    }
    .then { name in // observe
        print(name)
    }
  • 代碼變得十分易讀、簡潔,而且沒有嵌套,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/335197.html

標籤:其他

上一篇:Swift之深入決議如何使用Swift UI實作3D Scroll效果

下一篇:鴻蒙ide使用

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more