Promise 的幾種通用模式

作者:Soroush Khanlou,原文鏈接,原文日期:2016/8/8
譯者:Cwift;校對:Crystal Sun;定稿:CMB

譯者注:英文原文發(fā)布時間較早,故原文代碼中的 Swift 版本較舊,但是作者已將 GitHub 上的 Promise 示例代碼更新到了最新 Swift 版本,所以譯者在翻譯本文時,將文章里的代碼按照 GitHub 上的示例代碼進行了替換,更新成了最新版本的 Swift 代碼。

上周,我寫了一篇介紹 Promise 的文章,Promise 是處理異步操作的高階模塊。只需要使用 fulfill()、reject()then() 等函數(shù),就可以簡單自由地構(gòu)建大量的功能。本文會展示我在 Promise 方面的一些探索。

Promise.all

Promise.all 是其中的典型,它保存所有異步回調(diào)的值。這個靜態(tài)函數(shù)的作用是等待所有的 Promise 執(zhí)行 fulfill(履行) ,一旦全部執(zhí)行完畢,Promise.all 會使用所有履行后的值組成的數(shù)組對自己執(zhí)行 fulfill。例如,你可能想在代碼中對數(shù)組中的每個元素打點以捕獲某個 API 的完成狀態(tài)。使用 mapPromise.all 很容易實現(xiàn):

let userPromises = users.map({ user in
    APIClient.followUser(user)
})
Promise.all(userPromises).then({
    //所有的用戶都已經(jīng)執(zhí)行了 follow!
}).catch({ error in
    //其中一個 API 失敗了。
})

要使用 Promise.all,需要首先創(chuàng)建一個新的 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 }
            
    })
}

在這個 Promise 內(nèi)部,遍歷每個子 Promise,并分別為它們添加成功和失敗的處理流程。一旦有子 Promise 執(zhí)行失敗了,就可以拒絕高階的 Promise。

for promise in promises {
    promise.then({ value in

    }).catch({ error in
        reject(error)
    })
}

只有當所有的 Promise 都執(zhí)行成功,才可以 fulfill 高階的 Promise。檢查一下以確保沒有一個 Promise 被拒絕或者掛起,使用一點點 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)造成任何影響。

因為 Promise 是狀態(tài)機,它保存了與完成度有關(guān)的重要狀態(tài)。它是一種不同于 NSOperation 的方法。雖然 NSOperation 擁有一個完成回調(diào)以及操作的狀態(tài),但它不能保存得到的值,你需要自己去管理。

NSOperation 還持有線程模型以及優(yōu)先級順序相關(guān)的數(shù)據(jù),而 Promise 對代碼 如何 完成不做任何保證,只設(shè)置 完成后 需要執(zhí)行的代碼。Promise 類的定義足以證明。它唯一的實例變量是 state,狀態(tài)包括掛起、履行或者拒絕(以及對應(yīng)的數(shù)據(jù)),此外還有一個回調(diào)數(shù)組。(它還包含了一個隔離隊列,但那不是真正的狀態(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 或者其他方法來實現(xiàn)延遲,不過 asyncAfter 方法足夠簡單。當構(gòu)建其他有趣的 Promise 時,這個延遲 Promise 會很有用。

timeout

接下來,使用 delay 來構(gòu)建 timeout。該 Promise 如果超過一定時間就會被拒絕。

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" ]))
        })
    })
}

這個 Promise 自身沒有太多用處,但它可以幫助我們構(gòu)建一些其他功能的 Promise。

race

Promise.racePromise.all 的小伙伴,它不需要等待所有的子 Promise 完成,它只履行或者拒絕第一個完成的 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)
        }
    })
}

因為 Promise 只能被執(zhí)行或拒絕一次,所以當移除了 .pending 的狀態(tài)后,在外部對 Promise 調(diào)用 fulfill 或者 reject 不會產(chǎn)生任何影響。

有了這個函數(shù),使用 timeoutPromise.race 可以創(chuàng)建一個新的 Promise,針對成功、失敗或者超過了規(guī)定時間三種情況。把它定義在 Promise 的擴展中。

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
        //也許是超時引發(fā)的錯誤,也許是網(wǎng)絡(luò)錯誤
    })

這是我喜歡 Promise 的原因之一,它們的可組合性使得我們可以輕松地創(chuàng)建各種行為。通常需要保證 Promise 在 某個時刻 被履行或者拒絕,但是 timeout 函數(shù)允許我們用常規(guī)的方式來修正這種行為。

recover

recover 是另一個有用的函數(shù)。它可以捕獲一個錯誤,然后輕松地恢復狀態(tài),同時不會弄亂其余的 Promise 鏈。
我們很清楚這個函數(shù)的形式:它應(yīng)該接受一個函數(shù),該函數(shù)中接受錯誤并返回新的 Promise。recover 方法也應(yīng)該返回一個 Promise 以便繼續(xù)鏈接 Promise 鏈。

extension Promise {
    public func recover(_ recovery: @escaping (Error) throws -> Promise<Value>) -> Promise<Value> {
    
    }
}

在方法體中,需要返回一個新的 Promise,如果當前的 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ù)會返回一個新的 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)
            }
        })
    })
}

有了這個新的函數(shù)就可以從錯誤中恢復。例如,如果網(wǎng)絡(luò)沒有加載我們期望的數(shù)據(jù),可以從緩存中加載數(shù)據(jù):

APIClient.getUsers()
    .recover({ error in 
        return cache.getUsers()
    }).then({ user in
        //更新 UI
    }).catch({ error in
        //錯誤處理
    })

retry

重試是我們可以添加的另一個功能。若要重試,需要指定重試的次數(shù)以及一個能夠創(chuàng)建 Promise 的函數(shù),該 Promise 包含了重試要執(zhí)行的操作(所以這個 Promise 會被重復創(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)建一個包含了需要重試的 Promise 的新的 Promise,如果失敗了,在 delay 時間之后恢復到之前的狀態(tài)并重試,不過此時的重試次數(shù)減為 count - 1。

基于之前編寫的 delayrecover 函數(shù)構(gòu)建了重試的函數(shù)。

在上面的這些例子中,輕量且可組合的部分組合在一起,就得到了簡單優(yōu)雅的解決方案。所有的這些行為都是建立在 Promise 核心代碼所提供的簡單的 .thencatch 函數(shù)上的。通過格式化完成閉包的樣式,可以解決諸如超時、恢復、重試以及其他可以通過簡單可重用的方式解決的問題。這些例子仍然需要一些測試和驗證,我會在未來一段時間內(nèi)慢慢地添加到 GitHub 倉庫 中。

本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán),最新文章請訪問 http://swift.gg

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

相關(guān)閱讀更多精彩內(nèi)容

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