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 6662020-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提供的基本功能了,有几点需要注意的:
- OCPromise内的任务为保证线程安全及体现异步的特点,所有的任务都是在子线程执行的,因此对外的回调函数需注意线程问题,另外catch和finally的回调因为不涉及任务结果的传递,在内部强制切回了主线程进行执行。针对任务resolve的结果进行监听我提供了另外一个函数,将在下一篇文章进行介绍。
- OCPromise的创建在上面提过,仅提供Promise()及function()两种方式进行创建,由于function()函数Promise的创建延迟到上一个Promise执行完毕时,因此function()的构建方式不能用在首任务的创建。
- 如果在Promise()中既不实现resolve方法也不实现reject方法,则会造成任务队列传递的中断,对象无法释放而产生内存泄漏。
- 通过OCPromise.all执行的Promise返回的结果都是一个数组对象,需按照下标获取对应任务的结果,不能直接做为任务结果使用。即便传入的Promise数组是个空数组,返回的也是一个空数组对象,取值时如果下标越界则返回nil,不会崩溃。
- 任务队列的末尾如果需要收集处理结果时,需在末尾处连接.then(),由于需要接收上一个任务的结果,所以需要用到function()函数,而function()内部则不需要再创建Promise,直接return nil即可,这样实现的话后面就不能再连接.then()了,只能连接.catch()或.finally(),并且这三种实现需保证严格的顺序,即.then().catch().finally(),可以省略其中的任意几项,但不能重复实现。
以上就是OCPromise库的基本用法,可能没有Javascript的Promise那么灵活,希望看到的您能多给提提意见!下一篇文章我介绍一下针对OCPromise的基本实现做的一些扩展。谢谢!
github:OCPromise