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

相关文章
相关标签/搜索