了解完流程之后,就可以开始继续研究源码了。在PromiseKit当中,最常用的当属then,thenInBackground,catch,finally

- (PMKPromise *(^)(id))then {

return ^(id block){

return self.thenOn(dispatch_get_main_queue(), block);

};

}

- (PMKPromise *(^)(id))thenInBackground {

return ^(id block){

return self.thenOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);

};

}

- (PMKPromise *(^)(id))catch {

return ^(id block){

return self.catchOn(dispatch_get_main_queue(), block);

};

}

- (PMKPromise *(^)(dispatch_block_t))finally {

return ^(dispatch_block_t block) {

return self.finallyOn(dispatch_get_main_queue(), block);

};

}

这四个方法底层调用了各自的thenon,catchon,finallyon方法,这些on的方法实现基本都差不多,那我就以最重要的thenon来分析一下。

- (PMKResolveOnQueueBlock)thenOn {

return [self resolved:^(id result) {

if (IsPromise(result))

return ((PMKPromise *)result).thenOn;

if (IsError(result)) return ^(dispatch_queue_t q, id block) {

return [PMKPromise promiseWithValue:result];

};

return ^(dispatch_queue_t q, id block) {

block = [block copy];

return dispatch_promise_on(q, ^{

return pmk_safely_call_block(block, result);

});

};

}

pending:^(id result, PMKPromise *next, dispatch_queue_t q, id block, void (^resolve)(id)) {

if (IsError(result))

PMKResolve(next, result);

else dispatch_async(q, ^{

resolve(pmk_safely_call_block(block, result));

});

}];

}

这个thenon就是返回一个方法,所以继续往下看

- (id)resolved:(PMKResolveOnQueueBlock(^)(id result))mkresolvedCallback

pending:(void(^)(id result, PMKPromise *next, dispatch_queue_t q, id block, void (^resolver)(id)))mkpendingCallback

{

__block PMKResolveOnQueueBlock callBlock;

__block id result;

dispatch_sync(_promiseQueue, ^{

if ((result = _result))

return;

callBlock = ^(dispatch_queue_t q, id block) {

block = [block copy];

__block PMKPromise *next = nil;

dispatch_barrier_sync(_promiseQueue, ^{

if ((result = _result))

return;

__block PMKPromiseFulfiller resolver;

next = [PMKPromise new:^(PMKPromiseFulfiller fulfill, PMKPromiseRejecter reject) {

resolver = ^(id o){

if (IsError(o)) reject(o); else fulfill(o);

};

}];

[_handlers addObject:^(id value){

mkpendingCallback(value, next, q, block, resolver);

}];

});

return next ?: mkresolvedCallback(result)(q, block);

};

});

// We could just always return the above block, but then every caller would

// trigger a barrier_sync on the promise queue. Instead, if we know that the

// promise is resolved (since that makes it immutable), we can return a simpler

// block that doesn't use a barrier in those cases.

return callBlock ?: mkresolvedCallback(result);

}

这个方法看上去很复杂,仔细看看,函数的形参其实就是2个block,一个是resolved的block,还有一个是pending的block。当一个promise经历过resolved之后,可能是fulfill,也可能是reject,之后生成next新的promise,传入到下一个then中,并且状态会变成pending。上面代码中第一个return,如果next为nil,那么意味着promise没有生成,这是会再调用一次mkresolvedCallback,并传入参数result,生成的PMKResolveOnQueueBlock,再次传入(q, block),直到next的promise生成,并把pendingCallback存入到handler当中。这个handler存了所有待执行的block,如果把这个数组里面的block都执行,那么就相当于依次完成了上面的所有异步操作。第二个return是在callblock为nil的时候,还会再调一次mkresolvedCallback(result),保证一定要生成next的promise。

这个函数里面的这里dispatch_barrier_sync这个方法,就是promise后面可以链式调用then的原因,因为GCD的这个方法,让后面then变得像一行行的then顺序执行了。

可能会有人问了,并没有看到各个block执行,仅仅只是加到handler数组里了,这个问题的答案,就是promise的核心了。promise执行block的操作是放在resove里面的。先来看看源码

static void PMKResolve(PMKPromise *this, id result) {

void (^set)(id) = ^(id r){

NSArray *handlers = PMKSetResult(this, r);

for (void (^handler)(id) in handlers)

handler(r);

};

if (IsPromise(result)) {

PMKPromise *next = result;

dispatch_barrier_sync(next->_promiseQueue, ^{

id nextResult = next->_result;

if (nextResult == nil) {  // ie. pending

[next->_handlers addObject:^(id o){

PMKResolve(this, o);

}];

} else

set(nextResult);

});

} else

set(result);

}

这是一个递归函数,能形成递归的条件就是那句PMKResolve(this, o);当nextResult = nil的时候,就代表了这个promise还是pending状态,还没有被执行,这个时候就要递归调用,直到nextResult不为nil。不为nil,就会调用set方法,set方法是一个匿名函数,里面的for循环会依次循环,执行handler数组里面的每一个block。里面的那个if语句,是先判断result是否是一个promise,如果不是promise,就去执行set方法,依次调用各个block。

至此,一个then的执行原理就到此结束了。接下来我们再看看when的原理。

return newPromise = [PMKPromise new:^(PMKPromiseFulfiller fulfiller, PMKPromiseRejecter rejecter){

NSPointerArray *results = nil;

#if TARGET_OS_IPHONE

results = [NSPointerArray strongObjectsPointerArray];

#else

if ([[NSPointerArray class] respondsToSelector:@selector(strongObjectsPointerArray)]) {

results = [NSPointerArray strongObjectsPointerArray];

} else {

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wdeprecated-declarations"

results = [NSPointerArray pointerArrayWithStrongObjects];

#pragma clang diagnostic pop

}

#endif

results.count = count;

NSUInteger ii = 0;

for (__strong PMKPromise *promise in promises) {

if (![promise isKindOfClass:[PMKPromise class]])

promise = [PMKPromise promiseWithValue:promise];

promise.catch(rejecter(@(ii)));

promise.then(^(id o){

[results replacePointerAtIndex:ii withPointer:(__bridge void *)(o ?: [NSNull null])];

if (--count == 0)

fulfiller(results.allObjects);

});

ii++;

}

}];

这里只截取了return的部分,理解了then,这里再看when就好理解了。when就是在传入的promises的数组里面,依次执行各个promise,结果最后传给新生成的一个promise,作为返回值返回。

这里要额外提一点的就是如果给when传入一个字典,它会如何处理的

if ([promises isKindOfClass:[NSDictionary class]])

return newPromise = [PMKPromise new:^(PMKPromiseFulfiller fulfiller, PMKPromiseRejecter rejecter){

NSMutableDictionary *results = [NSMutableDictionary new];

for (id key in promises) {

PMKPromise *promise = promises[key];

if (![promise isKindOfClass:[PMKPromise class]])

promise = [PMKPromise promiseWithValue:promise];

promise.catch(rejecter(key));

promise.then(^(id o){

if (o)

results[key] = o;

if (--count == 0)

fulfiller(results);

});

}

}];

方式和when的数组方式基本一样,只不过多了一步,就是从字典里面先取出promise[key],然后再继续对这个promise执行操作而已。所以when可以传入以promise为value的字典。

五.使用PromiseKit优雅的处理回调地狱

这里我就举个例子,大家一起来感受感受用promise的简洁。

先描述一下环境,假设有这样一个提交按钮,当你点击之后,就会提交一次任务。首先要先判断是否有权限提交,没有权限就弹出错误。有权限提交之后,还要请求一次,判断当前任务是否已经存在,如果存在,弹出错误。如果不存在,这个时候就可以安心提交任务了。

void (^errorHandler)(NSError *) = ^(NSError *error) {

[[UIAlertView …] show];

};

[NSURLConnection sendAsynchronousRequest:rq queue:q completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

if (connectionError) {

errorHandler(connectionError);

} else {

NSError *jsonError = nil;

NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];

if (jsonError) {

errorHandler(jsonError);

} else {

id rq = [NSURLRequest requestWithURL:[NSURL URLWithString:json[@"have_authority"]]];

[NSURLConnection sendAsynchronousRequest:rq queue:q completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

NSError *jsonError = nil;

NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];

if (jsonError) {

errorHandler(jsonError);

} else {

id rq = [NSURLRequest requestWithURL:[NSURL URLWithString:json[@"exist"]]];

[NSURLConnection sendAsynchronousRequest:rq queue:q completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

NSError *jsonError = nil;

NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];

if (jsonError) {

errorHandler(jsonError);

} else {

if ([json[@"status"] isEqualToString:@"OK"]) {

[self submitTask];

} else {

errorHandler(json[@"status"]);

}

}

}];

}

}];

}

}

}];

上面的代码里面有3层回调,看上去就很晕,接下来我们用promise来整理一下。

[NSURLSession GET:url].then(^(NSDictionary *json){

return [NSURLConnection GET:json[@"have_authority"]];

}).then(^(NSDictionary *json){

return [NSURLConnection GET:json[@"exist"]];

}).then(^(NSDictionary *json){

if ([json[@"status"] isEqualToString:@"OK"]) {

return [NSURLConnection GET:submitJson];

} else

@throw [NSError errorWithDomain:… code:… userInfo:json[@"status"]];

}).catch(^(NSError *error){

[[UIAlertView …] show];

})

之前将近40行代码就一下子变成15行左右,看上去比原来清爽多了,可读性更高。

最后

看完上面关于PromiseKit的使用方法之后,其实对于PromiseKit,我个人的理解它就是一个Monad(这是最近很火的一个概念,4月底在上海SwiftCon 2016中,唐巧大神分享的主题就是关于Monad,还不是很了解这个概念的可以去他博客看看,或者找视频学习学习。)Promise就是一个盒子里面封装了一堆操作,then对应的就是一组flatmap或map操作。不过缺点也还是有,如果网络用的AFNetWorking,网络请求很有可能会回调多次,这时用PromiseKit,就需要自己封装一个属于自己的promise了。PromiseKit原生的是用的OMGHTTPURLRQ这个网络框架。PromiseKit里面自带的封装的网络请求也还是基于NSURLConnection的。所以用了AFNetWorking的同学,要想再优雅的处理掉网络请求引起的回调地狱的时候,自己还是需要先封装一个自己的Promise,然后优雅的then一下。很多人可能看到这里,觉得我引入一个框架,本来是来解决问题的,但是现在还需要我再次封装才能解决问题,有点不值得。

我自己的看法是,PromiseKit是个解决异步问题很优秀的一个开源库,尤其是解决回调嵌套,回调地狱的问题,效果非常明显。虽然需要自己封装AFNetWorking的promise,但是它的思想非常值得我们学习的!这也是接下来第二篇想和大家一起分享的内容,利用promise的思想,自己来优雅的处理回调地狱!这一篇PromiseKit先分享到这里。

如有错误,还请大家请多多指教。

iOS 如何优雅的处理“回调地狱Callback hell”(一) (下)的更多相关文章

  1. iOS 如何优雅的处理“回调地狱Callback hell”(一) (上)

    前言 最近看了一些Swift关于封装异步操作过程的文章,比如RxSwift,RAC等等,因为回调地狱我自己也写过,很有感触,于是就翻出了Promise来研究学习一下.现将自己的一些收获分享一下,有错误 ...

  2. js中的回调地狱 Callback to Hell

        本文重点:解决方式:1.promise  2. 拆解 function:将各步拆解为单个的 function  3. 通过 Generator 函数暂停执行的效果方式 4. 通过ES8的异步函 ...

  3. callback hell (回调地狱)

    callback hell (回调地狱) callback(回调) 如何修复 callback hell callback 回调只是存放一些即将要处理的代码. 回调的执行顺序不是从上到下的,而是根据事 ...

  4. js中promise解决callback回调地狱以及使用async+await异步处理的方法

    1.callback回调地狱 function ajax(fn) { setTimeout(()=> { console.log('你好') fn() }, 1000) } ajax(() =& ...

  5. 深入了解Promise对象,写出优雅的回调代码,告别回调地狱

    深入浅出了解Promise 引言 正文 一.Promise简介 二.Promise的三种状态 三.函数then( ) 四.函数catch( ) 五.函数finally( ) 六.函数all( ) 七. ...

  6. 避免Node.js中回调地狱

    为了解决这个阻塞问题,JavaScript严重依赖于回调,这是在长时间运行的进程(IO,定时器等)完成后运行的函数,因此允许代码执行经过长时间运行的任务. downloadFile('example. ...

  7. ES6(promise)_解决回调地狱初体验

    一.前言 通过这个例子对promise解决回调地狱问题有一个初步理解. 二.主要内容 1.回调地狱:如下图所示,一个回调函数里面嵌套一个回调函数,这样的代码可读性较低也比较恶心 2.下面用一个简单的例 ...

  8. JavaScript 中回调地狱的今生前世

    1. 讲个笑话 JavaScript 是一门编程语言 2. 异步编程 JavaScript 由于某种原因是被设计为单线程的,同时由于 JavaScript 在设计之初是用于浏览器的 GUI 编程,这也 ...

  9. 使用ES6的Promise完美解决回调地狱

    相信经常使用ajax的前端小伙伴,都会遇到这样的困境:一个接口的参数会需要使用另一个接口获取. 年轻的前端可能会用同步去解决(笑~),因为我也这么干过,但是极度影响性能和用户体验. 正常的前端会把接口 ...

随机推荐

  1. WPF之核心面板(容器)控件简单介绍

    一.Canvas 1.官方表述:定义一个区域,在该区域中可以使用相对于该区域的坐标显式定位子元素. 2.对于canvas 的元素的位置,是靠控件的大小及Canvas.Top.Canvas.Left.C ...

  2. GET和POST详解

    GET和POST 表单提交方式 http的get提交方法把表单数据编码到url中,可以在浏览器地址栏中看到, post提交把表单数据编码到http请求包的正文部分,在url中啊可能不到数据

  3. angularjs 实现排序功能

    实现公式:{{orderBy_expression | orderBy:expression:reverse}} Example <script> var app=angular.modu ...

  4. wndows常用命令

    1. 远程桌面 mstsc (Microsoft terminal services client)

  5. PLSQL Developer如何设置自动打开上次编辑的文件

    作为开发人员经常把sql语句保存到文件中以方便下次继续使用,问题是plsqlDev重启后每次都需要手工打开这个文件,好不方便: 以下设置是plsqlDev启动后自动打开上次编辑的文件. 选择配置> ...

  6. DOM this, currentTarget, Target

    http://www.w3cmm.com/javascript/this-currenttarget-target.html about mvc this one is not recommend:  ...

  7. Hibernate 的*.hbm.xml文件的填写技巧

    ================================================================================= 模板: <!-- ?属性,本类 ...

  8. TControl的显示函数(5个非虚函数,4个虚函数)和三个例子的执行过程(包括SetParent的例子)

    // 9个显示函数 procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); virtual; // 虚函数,important 根据父控 ...

  9. Effective C++学习笔记:初始化列表中成员列出的顺序和它们在类中声明的顺序相同

    类成员的默认初始化顺序是按照声明顺序进行, 如果使用初始化列表初始化成员变量, 则必须按照成员变量的声明顺序进行; 否则, 在变量之间交替赋值时, 会产生, 未初始化的变量去赋值其他变量; 同时GCC ...

  10. zabbix 四张大表分区

    trends_uint.ibd history history_unit trends CREATE TABLE `trends` ( `itemid` bigint(20) unsigned NOT ...