談一談Promise

本文將會向你簡單介紹promise,並在最後嘗試使用OC實現一個可用的promise庫。git

什麼是promise?

在面向對象的世界promise也並不特殊。promise對象表示異步操做的最終完成(或失敗)及其結果值。github

這樣一句話顯然沒法讓人理解promise,它是作什麼的?要怎麼使用它?爲何要使用它?下面的內容將會解釋這些問題。即便如今徹底不理解promise是什麼,請先記住「promise」這個字面含義給你帶來的暗示:某件事情(代碼)在將來的某個時刻發生(執行)。objective-c

咱們遇到的問題

一般咱們使用block回調來處理一些異步操做,好比:promise

[object doSomethingWithArg:arg handler:^(id data){  
    //resolve data
}];
複製代碼

上面的代碼沒有問題,然而假設咱們遇到了以下場景:發起網絡請求A>獲取網絡請求A的返回數據A>使用返回數據A做爲參數發起網絡請求B…如此往復,一般還包含着網絡錯誤的回調,代碼看起來是這樣的:網絡

//ViewController.m
- (void)viewDidLoad {
    [[XXNetwork shared] requestAWithArg:arg success:^(id dataA){  
        [[XXNetwork shared] requestBWithArg:dataA success:^(id dataB){  
            [[XXNetwork shared] requestCWithArg:dataB success:^(id dataC){  
                    //resolve dataC
            } failure:^(NSError *error){
                //網絡錯誤處理
            }];
        } failure:^(NSError *error){
            //網絡錯誤處理
        }];
    } failure:^(NSError *error){
        //網絡錯誤處理
    }];
}

//XXNetwork.h
//異步的網絡請求
- (void)requestAWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;
- (void)requestBWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;
- (void)requestCWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;


//XXNetwork.m
//原有的方法
- (void)requestAWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure {
	//doSomething
}
...
複製代碼

上面代碼看起來還算友好,可是當這樣的嵌套太深的時候,問題就出現了:異步

1.過多的嵌套形成代碼沒法被輕鬆的閱讀性能

2.網絡錯誤的情況沒有統一的處理學習

這樣場景下使用block回調嵌套顯然不夠優雅,那麼咱們要怎麼作?spa

使用promise改造代碼

對於咱們遇到的問題,promise將會大顯身手,使用promise改造後代碼看起來大概是這樣的:3d

//ViewController.m
- (void)viewDidLoad {
    [[XXNetwork shared] requestAWithArg:arg].then(^id(id value){
        //value 即 dataA
        return [[XXNetwork shared] requestBWithArg:value];
    }).then(^id(id value){
        //value 即 dataB
        return [[XXNetwork shared] requestCWithArg:value];
    }).then(^id(id value){
        //value 即 dataC
        //resolve dataC
    }).catch(^id(NSError * error){
        //網絡錯誤處理
    });
}



//XXNetwork.h
//原有的方法
//異步的網絡請求
- (void)requestAWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;
- (void)requestBWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;
- (void)requestCWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;


//包裝後的方法
- (Promise *)requestAWithArg:(id)arg;
- (Promise *)requestBWithArg:(id)arg;
- (Promise *)requestCWithArg:(id)arg;



//XXNetwork.m
//原有的方法
- (void)requestAWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure {
	//doSomething
}
...
//包裝後的方法
- (Promise *)requestAWithArg:(id)arg {
    Promise *p = [Promise new];
    [[XXNetwork shared] requestAWithArg:arg success:^(id dataA){
        p.fulfill(dataA);
    } failure:^(NSError *error){
    	p.reject(error);
    }];
    return p;
}
...
複製代碼

viewDidLoad中的代碼表示的是:發起網絡請求A,而後(then)使用返回數據A做爲參數發起網絡請求B,而後(then)使用返回數據B做爲參數發起網絡請求C,而後(then)處理dataC。catch則會處理鏈上產生的錯誤。你會發現,這段代碼閱讀下來很是貼近平常的語言習慣,可怕的回調地獄不見了,網絡錯誤有地方作統一處理,太酷了對不對?更重要的是咱們只須要作一些簡單的改造或包裝。

promise是怎麼運做的

在感嘆promise的優雅以後,咱們產生這些疑問:then是什麼?catch是什麼?value從哪裏來的?爲何須要返回一個promise對象?promise對象的fulfill和reject方法是幹什麼的?

爲了方便理解,先將viewDidLoad中的代碼縮減一部分,其餘不變,而後看一下執行過程

//ViewController.m
- (void)viewDidLoad {
    [[XXNetwork shared] requestAWithArg:arg].then(^id(id value){
        //value 即 dataA
    });
}

//XXNetwork.h
//原有的方法
//異步的網絡請求
- (void)requestAWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;
- (void)requestBWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;
- (void)requestCWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;


//包裝後的方法
- (Promise *)requestAWithArg:(id)arg;
- (Promise *)requestBWithArg:(id)arg;
- (Promise *)requestCWithArg:(id)arg;



//XXNetwork.m
//原有的方法
- (void)requestAWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure {
	//doSomething
}
...
//包裝後的方法
- (Promise *)requestAWithArg:(id)arg {
    Promise *p = [Promise new];
    [[XXNetwork shared] requestAWithArg:arg success:^(id dataA){
        p.fulfill(dataA);
    } failure:^(NSError *error){
    	p.reject(error);
    }];
    return p;
}
...
複製代碼

1.在viewDidLoad執行後,執行XXNetwork單例requestAWithArg:方法

2.在requestAWithArg:方法內建立了一個promise對象。調用原有的方法requestAWithArg:success:failure:,若是異步請求成功則將會調用promise的fulfill方法,失敗則將會調用reject方法。返回這個promise對象

3.調用promise的then方法,將成功後的須要執行的block加入到promise中

4.最後當requestAWithArg:success:failure:進入成功回調則調用promise的fulfill方法,使用promise的then方法加入的block會執行。同理當requestAWithArg:success:failure:進入失敗回調則調用promise的reject方法,使用promise的catch方法加入的block就會執行

回想一開始咱們對promise對象的描述:promise對象表示異步操做的最終完成(或失敗)及其結果值。如今腦海中有一些輪廓正在出現,讓咱們結合下面圖將它梳理清晰:

promise對象始終處於如下3個狀態之一:

  • pending初始化狀態

  • fulfilled表示操做已經完成

  • rejected表示操做已經失敗

當咱們建立一個promise對象時,promise處於pending狀態;咱們能夠經過promise的then,catch等方法將成功或失敗後須要執行的任務(block)加入到promise的「回調列表」;當異步操做完成後調用promise的fulfill或reject方法,並傳遞參數;promise的狀態從pending轉換到fulfilled或rejected,這樣的轉換是不可逆的,同時會調用以前使用的then或者catch加入到該promise任務(block);then或者catch方法將會返回一個新的promise對象,咱們能夠對新的promise繼續調用then或catch方法,從而造成了鏈式調用。這就是promise的核心部分。

目前爲止僅簡單介紹了promise的一部分,再也不作更多的使用介紹,經過如下網址能夠獲取更多的關於promise的規範和使用方法

developer.mozilla.org/en-US/docs/…

promisesaplus.com/

在代碼中使用promise以前

Promise並不是OC原生提供,使用PromiseKit是一個好的選擇,它有豐富可靠的API,你能夠在這裏找到它,具體的使用方法參考其文檔。

使用promise讓咱們遠離了回調地獄,可是咱們能夠思考下一些問題要如何應對:失去了參數的類型信息要如何處理?promise是否有性能問題?可否停止一個promise的鏈?引入promise的學習成本有多少等等。這些會問題能夠在實踐中解開,找到適合使用的場景,作好權衡。

實現一個可用promise庫

在瞭解一些規範後咱們能夠嘗試本身實現一個可用的庫ToyPromise,你會看到then,catch,finally,race,all這些熟悉的API的具體實現。因爲一些緣由使用上和promise的規範有一些差別,但核心的部分是不變的。ToyPromise目前僅是個玩具,歡迎貢獻代碼讓它成長。

相關文章
相關標籤/搜索