OCPromise是参考的Javescript中的Promise写的一套用Objective-C实现的异步任务队列管理的链式操作工具。

写这套库的想法是源自一次面试的失败经历:之前在工作中我使用过React native进行开发,因此也写过Javascript代码并且使用过Promise语法,但是在一次面试中,面试官让我手写Promise的实现,当时我直接懵了,才发现在开发过程中很多东西实现过一次之后,后面再用到时直接复制粘贴再改一改,结果就是这些东西根本没有变成自己的知识,甚至对它的理解也很片面。

回想起来,Promise的调用方式还是很有意思的,链式的语法使用起来也很美观,因此我尝试用OC实现了Promise的功能,OCPromise提供的功能可以参考这篇关于JS-Promise的文章:理解 Javascript 中的 Promise,我在写OCPromise时也是完全参照的Promise,只不过由于语法的差异性,调用方法会略有不同。

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

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


DOS魔王
1 声望0 粉丝