iOS中Promise的使用

Promsie的概念是我在學(xué)習(xí)前端時(shí)接觸到的概念,簡而言之就是避免地獄回調(diào),在異步編程時(shí)例如網(wǎng)絡(luò)請(qǐng)求時(shí),以往對(duì)于異步任務(wù)完成時(shí)的結(jié)果往往采取回調(diào)的方式,在OC中采取block的形式,swiftJavaScript則可以采用閉包的形式,但是一旦我們的業(yè)務(wù)比較復(fù)雜,就會(huì)出現(xiàn)地獄回調(diào),如下情況所示:

Promise的初步了解

我們來考慮下面的場(chǎng)景(有夸張的成分):

  • 我們需要通過一個(gè)url1從服務(wù)器加載一個(gè)數(shù)據(jù)data1data1中包含了下一個(gè)請(qǐng)求的url2。
  • 我們需要通過data1取出url2,從服務(wù)器加載數(shù)據(jù)data2,data2中包含了下一個(gè)請(qǐng)求的url3。
  • 我們需要通過data2取出url3,從服務(wù)器加載數(shù)據(jù)data3,data3中包含了下一個(gè)請(qǐng)求的url4。
  • 發(fā)送網(wǎng)絡(luò)請(qǐng)求url4,獲取最終的數(shù)據(jù)data4。

在前端中我們會(huì)寫出如下代碼:

$.ajax('url1',function (data1){
   $.ajax(data1['url2'],function (data2){
       $.ajax(data2['url3'],function (data3){
            $.ajax(data3['url4'],function (data4){
               console.log(data4);
           })
       })
   })
})

接下來我們看使用Promise后的效果,ES6開始提供Promise特性的支持:

  new Promise((resolve, reject) => {
    $.ajax('url1',function (data1) {
      resolve(data1)
    })
  }).then(data1 => {
    return new Promise((resolve, reject) => {
      $.ajax('data1[url2]',function (data2) {
        resolve(data2)
      })
    })
  }).then(data2 => {
    return new Promise((resolve, reject) => {
      $.ajax('data2[url3]',function (data3) {
        resolve(data3)
      })
    })
  }).then(data3 => {
    return new Promise((resolve, reject) => {
      $.ajax('data3[url4]',function (data4) {
        console.log(data4)
      })
    })
  })

經(jīng)過Promise操作后,復(fù)雜嵌套異步操作的回調(diào)結(jié)構(gòu)變得清晰了許多,通過resolve這個(gè)閉包將請(qǐng)求的結(jié)果返回到外層,通過鏈?zhǔn)?strong>then方法的調(diào)用拿到上層resolve返回的結(jié)果,再次通過返回一個(gè)Promise對(duì)象并對(duì)拿到的結(jié)果進(jìn)行一些需要的異步操作,這樣的結(jié)構(gòu)使得異步任務(wù)清晰了然。

Swift 中Promise的實(shí)現(xiàn)

Swift中已有成熟的PromiseKit三方庫支持Promise,本文主要是分析此文Swift 中實(shí)現(xiàn) Promise 模式手寫的一個(gè)簡易的Promise模式的實(shí)現(xiàn),主要是利用Swift的閉包以及鏈?zhǔn)秸{(diào)用的思想來實(shí)現(xiàn),由于原文已有實(shí)現(xiàn)原理,那么廢話不多說直接上代碼進(jìn)行分析,代碼里標(biāo)有注釋,同時(shí)本文代碼對(duì)原文代碼進(jìn)行了一定的修改,因?yàn)樵牡拇a有點(diǎn)小問題:


import UIKit
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        delay(secs: 5)
            .then { (string) -> Promise<Data?> in
                // 拿到結(jié)果進(jìn)行處理,并進(jìn)行向下傳遞
                self.fetch(URL: URL(string: string)!)
            }
            .then { (data) -> Promise<String> in
                // 拿到結(jié)果進(jìn)行處理,并進(jìn)行向下傳遞
                self.decodeToString(data: data)
            }
            .startTask(success: { result in
                print(result + "-------------")
            }, failed: { Error in
                print(Error)
            })
    }

    func delay(secs: TimeInterval = 3) -> Promise<String> {
        // 這里是swift的尾隨閉包寫法,直接調(diào)用了init初始化方法返回了一個(gè)promise對(duì)象
        // 這個(gè)promise對(duì)象的self.task為傳入的閉包
        return Promise<String> { (resolve, _) in
            DispatchQueue.main.asyncAfter(deadline: .now() + secs) {
                // 異步操作成功調(diào)用resolve回調(diào),失敗調(diào)用reject,這里只模擬成功的情況
                resolve("https://www.zhihu.com1")
            };
            
        }
    }

    func fetch(URL: URL) -> Promise<Data?> {
        return Promise<Data?> { (resolve, reject) in
            let request = URLRequest(url: URL)
            let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
                if (error != nil) {
                    reject(error!)
                } else {
                    /* 這里的resolve其實(shí)就是{ self.resolve(result: $0) } 這個(gè)閉包里的resolove是函數(shù),會(huì)調(diào)用閉包 self.resolveCallback?(result) 而resolveCallback就是調(diào)用startTask傳入的閉包{ resolve($0) }
                     而最后的這個(gè)resolve就是后面調(diào)用then確定的也就是:這里的result為網(wǎng)址返回值
                    (result) in
                    let wrapped = f(result)
                    wrapped.success { resolve($0) }
                    */
                    resolve(data)
                }
            }
            task.resume()
        }
    }

    func decodeToString(data: Data?) -> Promise<String> {
        return Promise<String> { (resolve, _) in
            if (data == nil) {
                resolve("")
            } else {
                /*
                 這里的resolve其實(shí)就是{ self.resolve(result: $0) } 這個(gè)閉包里的resolove是函數(shù),會(huì)調(diào)用閉包 self.resolveCallback?(result) 而resolveCallback就是調(diào)用success傳入的閉包{ resolve($0) }
                  而最后的這個(gè)startTask確定的:這里的resolve傳入的值是String函數(shù)處理過的
                 (result) in
                     print(result + "--------------")
                 所以進(jìn)行了最后的打印
                 */      
                resolve(String(data: data!, encoding: String.Encoding.utf8) ?? "")
            }
        }
    }

}

class Promise<T> {
    typealias ResolveCallback = (T) -> Void
    typealias RejectCallback = (Error) -> Void
    typealias AsyncTask = (@escaping ResolveCallback,@escaping RejectCallback) -> Void
    
    let task: AsyncTask
    
    var resolveCallback: ResolveCallback?
    var rejectCallback: RejectCallback?
    
    init(_ task: @escaping AsyncTask) {
        self.task = task
    }
    
    private func resolve(result: T) {
        self.resolveCallback?(result)
    }
    
    private func reject(error: Error) {
        self.rejectCallback?(error)
    }
    
    func startTask(success: @escaping ResolveCallback,failed:@escaping RejectCallback) {
        self.resolveCallback = success
        self.rejectCallback = failed
        self.task({ self.resolve(result: $0) }, { self.reject(error: $0) })
    }
    
    func then<U>(f:@escaping (T) -> Promise<U>) -> Promise<U> {
        return Promise<U> { (resolve, reject) in
            self.task(
                { (result) in
                    // result = ”“ 和String函數(shù)處理過的值
                    let wrapped = f(result)
                    // wrapped是promise對(duì)象,通過調(diào)用startTask來執(zhí)行內(nèi)部異步任務(wù),然后回調(diào)了then內(nèi)部Promise的reslove,而這個(gè)resolve和failed又會(huì)由下一個(gè)then來確定
                    wrapped.startTask(success: {resolve($0)}, failed: {reject($0)})
                },
                { (error) in
                    reject(error)
            })
        }
    }
}

代碼分析

promise類結(jié)構(gòu)分析

  • 首先是創(chuàng)建了一個(gè)promise的類,此類初始化需要傳入一個(gè)代表異步任務(wù)的閉包,異步任務(wù)的閉包有二個(gè)參數(shù),二個(gè)參數(shù)也為二個(gè)閉包,且參數(shù)都采用了泛型。
  • startTask方法用來啟動(dòng)異步任務(wù),也稱之為冷啟動(dòng),只有調(diào)用了startTask才會(huì)啟動(dòng)Promise的異步任務(wù)。
  • then方法是用來鏈?zhǔn)秸{(diào)用的,當(dāng)promise對(duì)象調(diào)用then方法時(shí)會(huì)依然返回一個(gè)promise對(duì)象,這樣可以繼續(xù)調(diào)用then方法繼而鏈?zhǔn)秸{(diào)用下去。

代碼執(zhí)行流程分析

  • 在調(diào)用delay(secs: 5)方法時(shí),返回的是一個(gè)promise對(duì)象,這個(gè)promise傳入了一個(gè)閉包代表異步任務(wù),并用self.task進(jìn)行了保存,這里異步任務(wù)采用延時(shí)的方式進(jìn)了模擬,實(shí)際開發(fā)中可能為網(wǎng)絡(luò)請(qǐng)求,同時(shí)此閉包有二個(gè)參數(shù),分別為resolvereject,分別代表異步任務(wù)成功時(shí)和失敗時(shí)的回調(diào),也就是上面promise類結(jié)構(gòu)分析中的第一點(diǎn),且由于沒有失敗的情況,這里直接調(diào)用了resolve成功時(shí)的回調(diào)。
  • delay(secs: 5)返回的promise對(duì)象的異步任務(wù)需要傳入的resolvereject只有在調(diào)用的時(shí)候才能確定,何時(shí)開始調(diào)用呢?其實(shí)對(duì)于promise對(duì)象來說,啟動(dòng)異步任務(wù)的方法只有一個(gè)就是調(diào)用startTask, 在鏈?zhǔn)秸{(diào)用then方法時(shí)返回的依然是一個(gè)promise對(duì)象,而這個(gè)promise對(duì)象不同點(diǎn)在于其異步任務(wù)是執(zhí)行了調(diào)用then前的promise對(duì)象的self.task并傳遞了成功時(shí)回調(diào)和失敗時(shí)回調(diào)二個(gè)閉包,而傳遞的這個(gè)成功時(shí)的閉包則很有講究,當(dāng)then之前的promise對(duì)象執(zhí)行resolve時(shí),會(huì)拿到這個(gè)promise對(duì)象異步執(zhí)行的結(jié)果,然后將這個(gè)參數(shù)順勢(shì)傳入執(zhí)行了調(diào)用then方法時(shí)傳入的閉包f并執(zhí)行,此時(shí)依然返回一個(gè)promise對(duì)象,我們想要的是執(zhí)行這個(gè)promise對(duì)象內(nèi)部的異步任務(wù),并且把異步任務(wù)的結(jié)果拋出來,怎么辦?肯定還是得調(diào)動(dòng)startTask呀,為了將結(jié)果從調(diào)用then產(chǎn)生的promise對(duì)象拋出去,這里的startTask傳入的二個(gè)回調(diào)閉包則要主動(dòng)調(diào)用調(diào)用then時(shí)初始化promsie時(shí)定義的resolvereject。
  • then的鏈?zhǔn)秸{(diào)用就好像用炸藥開山時(shí),用引線將炸藥包首尾相連,直到最后一個(gè)炸藥包埋進(jìn)山體的深度達(dá)到了炸開山體的深度,則點(diǎn)燃第一個(gè)炸藥的引線,炸藥環(huán)環(huán)爆炸炸開山體。promise對(duì)像通過then相連,當(dāng)掛載startTask時(shí)則會(huì)執(zhí)行第一個(gè)promise對(duì)象的異步任務(wù),并將異步任務(wù)結(jié)果向下傳遞到下一個(gè)promise對(duì)象。

總結(jié)

Promise能讓多層嵌套的異步任務(wù)結(jié)構(gòu)變得更加清晰,本文是實(shí)現(xiàn)了Swiftpromise方式,和ES6中的promise使用起來還是稍微麻煩點(diǎn),但是核心思想就是鏈?zhǔn)秸{(diào)用配合閉包來實(shí)現(xiàn),仔細(xì)體會(huì)會(huì)發(fā)現(xiàn)別有洞天,當(dāng)然也可以直接用三方框架簡單明了。

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

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

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