OCPromise-用OC實現JS的Promise

OCPromise是參考的Javescript中的Promise寫的一套用Objective-C實現的異步任務隊列管理的鏈式操做工具。git

寫這套庫的想法是源自一次面試的失敗經歷:以前在工做中我使用過React native進行開發,所以也寫過Javascript代碼而且使用過Promise語法,可是在一次面試中,面試官讓我手寫Promise的實現,當時我直接懵了,才發如今開發過程當中不少東西實現過一次以後,後面再用到時直接複製粘貼再改一改,結果就是這些東西根本沒有變成本身的知識,甚至對它的理解也很片面。github

回想起來,Promise的調用方式仍是頗有意思的,鏈式的語法使用起來也很美觀,所以我嘗試用OC實現了Promise的功能,OCPromise提供的功能能夠參考這篇關於JS-Promise的文章:理解 Javascript 中的 Promise,我在寫OCPromise時也是徹底參照的Promise,只不過因爲語法的差別性,調用方法會略有不一樣。面試

下面我先介紹一下OCPromise的使用方法:segmentfault

OCPromise的建立

OCPromise *p = Promise(^(resolve  _Nonnull resolve, reject  _Nonnull reject) {
        NSLog(@"start new Promise...");
        resolve(@123);
    });
    
    OCPromise *multiply = function(^OCPromise * _Nullable(id  _Nonnull value) {
        return Promise(^(resolve  _Nonnull resolve, reject  _Nonnull reject) {
            NSLog(@"calculating %ld x %ld ...", [value longValue], [value longValue]);
            resolve([NSNumber numberWithLong:[value longValue] * [value longValue]]);
        });
    });
    
    OCPromise *add = function(^OCPromise * _Nullable(id  _Nonnull value) {
        return Promise(^(resolve  _Nonnull resolve, reject  _Nonnull reject) {
            NSLog(@"calculating %ld + %ld ...", [value longValue], [value longValue]);
            resolve([NSNumber numberWithLong:[value longValue] + [value longValue]]);
        });
    });

使用Promise()函數建立的Promise對象能夠處理獨立的任務,而使用function()函數建立Promise對象時,實際Promise對象的建立延遲到^OCPromise *(id value) {}執行時期,而且Promise的任務執行期間value能夠參與內部的Promise()任務的執行的(也能夠不參與)。數組

OCPromise對象的串聯

p.
    then(multiply).
    then(add).
    then(multiply).
    then(add).
    then(function(^OCPromise * _Nullable(id  _Nonnull value) {
        NSLog(@"Got value: %@",value);
        return nil;
    }));

打印結果安全

2020-05-29 15:33:34.955691+0800 OCPromise_Example[80577:17114562] start new Promise...
2020-05-29 15:33:34.956269+0800 OCPromise_Example[80577:17114562] calculating 123 x 123 ...
2020-05-29 15:33:34.957493+0800 OCPromise_Example[80577:17114562] calculating 15129 + 15129 ...
2020-05-29 15:33:34.958875+0800 OCPromise_Example[80577:17114562] calculating 30258 x 30258 ...
2020-05-29 15:33:34.960475+0800 OCPromise_Example[80577:17114562] calculating 915546564 + 915546564 ...
2020-05-29 15:33:34.961727+0800 OCPromise_Example[80577:17114562] Got value: 1831093128

能夠看到,當Promise執行了resolve(),任務確實被串了起來順序的執行。而且這裏咱們須要注意,使用function()函數構建Promise函數時,^OCPromise *(id value) {}並不能經過外部進行觸發執行,而是由上一個Promise對象執行完resolve()進行觸發的。併發

OCPromise的reject與catch與finally

剛纔示例中Promise都是執行的resolve(),這表示任務處理成功,而對應的reject()則是觸發異常狀況,針對任務隊列的異常捕獲咱們要用到catch()函數。
finally()函數是在任務隊列執行完畢後觸發執行的,不管整個任務隊列成功完成仍是出現了異常都會執行,咱們能夠在這裏進行一些最終處理,好比加載動畫的關閉或者最終數據的處理等。
下面咱們來看一下reject與catch的配合使用以及finally的使用方法異步

//增長一個觸發reject的Promise對象
    OCPromise *doReject = function(^OCPromise * _Nullable(id  _Nonnull value) {
        return Promise(^(resolve  _Nonnull resolve, reject  _Nonnull reject) {
            NSLog(@"receive %ld",[value longValue]);
            if ([value longValue] > 1000) {
                reject(@"opps, number is too big");
            } else {
                resolve(value);
            }
        });
    });

    p.
    then(multiply).
    then(doReject).
    then(add).
    catch(^(id  _Nonnull value) {
        NSLog(@"catch error, reason is \"%@\"",value);
    }).
    finally(^(id  _Nonnull value) {
        NSLog(@"final value is \"%@\"",value);
    });

打印結果函數

2020-05-29 16:17:49.402107+0800 OCPromise_Example[80859:17146759] start new Promise...
2020-05-29 16:17:49.402549+0800 OCPromise_Example[80859:17146759] calculating 123 x 123 ...
2020-05-29 16:17:49.403076+0800 OCPromise_Example[80859:17146759] receive 15129
2020-05-29 16:17:49.403401+0800 OCPromise_Example[80859:17146759] catch error, reason is "opps, number is too big"
2020-05-29 16:17:49.403814+0800 OCPromise_Example[80859:17146759] final value is "opps, number is too big"

在執行到doReject時入參value大於1000,執行了reject(),所以後面的add並無執行,直接執行了catch和finally。工具

OCPromise的靜態方法

OCPromise.resolve

OCPromise.resolve(@"Just do it!").then(function(^OCPromise * _Nullable(id  _Nonnull value) {
        NSLog(@"%@",value);
        return nil;
    }));
2020-05-29 16:29:39.036376+0800 OCPromise_Example[80944:17156364] Just do it!

OCPromise. reject

OCPromise.reject(@"Oops!").catch(^(id  _Nonnull value) {
        NSLog(@"%@",value);
    });
2020-05-29 16:31:39.463002+0800 OCPromise_Example[80971:17158013] Oops!

OCPromise.resolve和OCPromise. reject其實就是兩個簡單的觸發器,是建立單一指責任務模塊的快捷方式,由這兩個靜態方法建立的Promise對象不受外部條件的影響,而且僅能觸發正常執行/拋出異常一種模式。
應用場景例如OCPromise.resolve能夠做爲任務隊列的觸發函數:

OCPromise.resolve(@123).then(multiply).then(add);

或者:

p.then(function(^OCPromise * _Nullable(id  _Nonnull value) {
        if ([value longValue]>1000) {
            return OCPromise.resolve(value);    //here!!!
        } else {
            return OCPromise.reject(@"Oops,got error");    //here!!!
        }
    })).then(function(^OCPromise * _Nullable(id  _Nonnull value) {
        NSLog(@"got value %@", value);
        return nil;
    })).catch(^(id  _Nonnull value) {
        NSLog(@"catch error %@",value);
    });

OCPromise.all

OCPromise.all接收一組容納Promise對象的數組,並將這些Promise對象打包生成一個新的Promise對象,這個Promise被觸發執行時,內部的Promise數組中的任務開始異步併發執行,並在全部任務都完成時回調完成,這部分和GCD的dispatch_group_notify相似。
多個任務中只要有一個任務出現異常,則會執行reject拋出第一個發生的異常。
若是接收到一個空數組則直接執行resolve。

OCPromise *task1 = function(^OCPromise * _Nullable(id  _Nonnull value) {
        return Promise(^(resolve  _Nonnull resolve, reject  _Nonnull reject) {
            NSLog(@"task1 needs sleep 4sec");
            sleep(4);
            NSLog(@"task1 woke up");
            resolve([NSString stringWithFormat:@"task1 checked %@",value]);
        });
    });
    
    OCPromise *task2 = Promise(^(resolve  _Nonnull resolve, reject  _Nonnull reject) {
        NSLog(@"task2 needs sleep 1sec");
        sleep(1);
        NSLog(@"task2 woke up");
        resolve(@"task2 is fine");
    });
    
    OCPromise *task3 = function(^OCPromise * _Nullable(id  _Nonnull value) {
        return Promise(^(resolve  _Nonnull resolve, reject  _Nonnull reject) {
            NSLog(@"task3 needs sleep 3sec");
            sleep(3);
            NSLog(@"task3 wokeup");
            resolve([NSString stringWithFormat:@"task3 ignored %@",value]);
        });
    });
    
    OCPromise *all = OCPromise.all(@[task1, task2, task3]);
    
    OCPromise.resolve(@"the wallet").then(all).then(function(^OCPromise * _Nullable(id  _Nonnull value) {
        NSLog(@"got value %@", value);
        return nil;
    }));
2020-06-01 17:51:42.608045+0800 OCPromise_Example[89417:18881922] task1 needs sleep 4sec
2020-06-01 17:51:42.608099+0800 OCPromise_Example[89417:18881919] task3 needs sleep 3sec
2020-06-01 17:51:42.608132+0800 OCPromise_Example[89417:18881925] task2 needs sleep 1sec
2020-06-01 17:51:43.609261+0800 OCPromise_Example[89417:18881925] task2 woke up
2020-06-01 17:51:45.609812+0800 OCPromise_Example[89417:18881919] task3 wokeup
2020-06-01 17:51:46.612289+0800 OCPromise_Example[89417:18881922] task1 woke up
2020-06-01 17:51:46.613935+0800 OCPromise_Example[89417:18881920] got value (
task1 checked the wallet,
task2 is fine,
task3 ignored the wallet

)

能夠看到,Promise數組因爲是異步併發的,因此任務執行的順序是隨機不固定的,三個任務耗時分別是4秒、1秒、3秒,最終執行完畢時耗時4秒,並返回一個結果數組,數組內值的順序和Promise數組的任務順序一致。
爲保證結果數組內值的順序,而且支持存儲nil值,其實這裏返回的數組是一個自定義的NSObject對象,並實現了數組的一些簡單調用方法:經過下標進行取值value[0]、value[1],objectAtIndex,forin,enumerateObjectsUsingBlock。
Promise數組內也支持直接傳值,內部會轉成OCPromise.resolve。

OCPromise *all = OCPromise.all(@[@"Goodjob", @666, OCPromise.resolve(nil)]);
    all.then(function(^OCPromise * _Nullable(id  _Nonnull value) {
        NSLog(@"first obj %@", value[0]);
        NSLog(@"second obj %@", [value objectAtIndex:1]);
        for (id obj in value) {
            NSLog(@"forin obj %@",obj);
        }
        [value enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSLog(@"enumerate block at %ld obj %@",idx, obj);
        }];
        return nil;
    }));
2020-06-01 18:08:47.032494+0800 OCPromise_Example[89678:18896195] first obj Goodjob
2020-06-01 18:08:47.033058+0800 OCPromise_Example[89678:18896195] second obj 666
2020-06-01 18:08:47.033555+0800 OCPromise_Example[89678:18896195] forin obj Goodjob
2020-06-01 18:08:47.034477+0800 OCPromise_Example[89678:18896195] forin obj 666
2020-06-01 18:08:47.035326+0800 OCPromise_Example[89678:18896195] forin obj (null)
2020-06-01 18:08:47.036120+0800 OCPromise_Example[89678:18896195] enumerate block at 0 obj Goodjob
2020-06-01 18:08:47.036878+0800 OCPromise_Example[89678:18896195] enumerate block at 1 obj 666
2020-06-01 18:08:47.037486+0800 OCPromise_Example[89678:18896195] enumerate block at 2 obj (null)

OCPromise.race

OCPromise.race也是多任務併發處理的集合,Promise的建立過程和OCPromise.all相同,只不過完成的條件再也不是全部任務所有完成,而是競爭模式,當任意一個任務率先完成,不管成功仍是失敗,都會直接將該結果回調,其他任務結果則丟棄再也不處理。

OCPromise.race(@[@666, OCPromise.reject(@"oops")]).then(function(^OCPromise * _Nullable(id  _Nonnull value) {
        NSLog(@"got value %@", value);
        return nil;
    })).catch(^(id _Nonnull value) {
        NSLog(@"got error %@", value);
    });

兩次不一樣的返回結果:

2020-05-29 18:14:43.770779+0800 OCPromise_Example[81758:17233041] got value 666

2020-05-29 18:13:13.503533+0800 OCPromise_Example[81745:17231231] got error oops

應用的場景例如經過不一樣的接口請求相同的資源,或者爲某個耗時操做添加超時操做等。

OCPromise *dealTask = Promise(^(resolve  _Nonnull resolve, reject  _Nonnull reject) {
        sleep(5);  //模擬耗時操做
        resolve(@"done");
    });
    
    OCPromise *timeout = Promise(^(resolve  _Nonnull resolve, reject  _Nonnull reject) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            reject(@"time out");
        });
    });
    NSLog(@"task start");
    OCPromise.race(@[dealTask, timeout]).then(function(^OCPromise * _Nullable(id  _Nonnull value) {
        NSLog(@"result is %@", value);
        return nil;
    })).catch(^(id _Nonnull value) {
        NSLog(@"%@", value);
    });
2020-05-29 18:22:01.598934+0800 OCPromise_Example[81826:17238770] task start
2020-05-29 18:22:04.601462+0800 OCPromise_Example[81826:17238841] time out

以上就是OCPromise提供的基本功能了,有幾點須要注意的:

  1. OCPromise內的任務爲保證線程安全及體現異步的特色,全部的任務都是在子線程執行的,所以對外的回調函數需注意線程問題,另外catch和finally的回調由於不涉及任務結果的傳遞,在內部強制切回了主線程進行執行。針對任務resolve的結果進行監聽我提供了另一個函數,將在下一篇文章進行介紹。
  2. OCPromise的建立在上面提過,僅提供Promise()及function()兩種方式進行建立,因爲function()函數Promise的建立延遲到上一個Promise執行完畢時,所以function()的構建方式不能用在首任務的建立。
  3. 若是在Promise()中既不實現resolve方法也不實現reject方法,則會形成任務隊列傳遞的中斷,對象沒法釋放而產生內存泄漏。
  4. 經過OCPromise.all執行的Promise返回的結果都是一個數組對象,需按照下標獲取對應任務的結果,不能直接作爲任務結果使用。即使傳入的Promise數組是個空數組,返回的也是一個空數組對象,取值時若是下標越界則返回nil,不會崩潰。
  5. 任務隊列的末尾若是須要收集處理結果時,需在末尾處鏈接.then(),因爲須要接收上一個任務的結果,因此須要用到function()函數,而function()內部則不須要再建立Promise,直接return nil便可,這樣實現的話後面就不能再鏈接.then()了,只能鏈接.catch()或.finally(),而且這三種實現需保證嚴格的順序,即.then().catch().finally(),能夠省略其中的任意幾項,但不能重複實現。

以上就是OCPromise庫的基本用法,可能沒有Javascript的Promise那麼靈活,但願看到的您能多給提提意見!下一篇文章我介紹一下針對OCPromise的基本實現作的一些擴展。謝謝!

OCPromise-進階用法

github:OCPromise

相關文章
相關標籤/搜索