回调地狱

上次说写了个顺序执行异步任务的,但有些情况并不适用,比如用登录后的token获取一些登录后没有返回的用户信息,再用这些信息请求对应的内容显示,这里有三次网络请求,依赖上一次的请求状态。显然上次写的那坨代码不能满足这个需求。

具体问题

有个名词定义上述的情况,回调地狱,给一张传说中以前的滴滴js代码截图

eece3195cb41be76

真特么人才,真心的。

其实我之前的做法也不比这个好到哪里去,当遇到网络回调嵌套的时候,就把嵌套的代码抽成方法,调用即可,这样无论有多少网络回调依赖,代码看起来没有回调地狱那么夸张了,但是这样会造成什么问题呢?

代码易读性并没有提高多少,拿上面登录那个举例,从调用上看,调用登录那一块代码,后面的获取用户信息和获取显示内容都会接着调用,别人需要到具体的方法回调里跟着一行一行看才知道是这么回事。除此之外,想要只登录,或者只获取用户信息,是没法重用这些方法的。

PromiseKit

如果只是为了解决回调地狱这个问题,PromiseKit足以胜任。

一般调用

本来,我们一般用block执行异步回调,方法类似下面这样

1
2
3
4
5
6
7
8
9
10
11
12
- (void)sleep:(int)i success:(void (^)(void))sucess failed:(void (^)(void))failed{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(arc4random()%2+1);
NSLog(@"%d", i);

if (i==4) {
failed();
}else {
sucess();
}
});
}

嵌套调用如下

1
2
3
4
5
6
7
8
9
10
11
12
13
[self sleep:1 success:^{
[self sleep:2 success:^{
[self sleep:3 success:^{
NSLog(@"sucess");
} failed:^{
NSLog(@"failed");
}];
} failed:^{
NSLog(@"failed");
}];
} failed:^{
NSLog(@"failed");
}];

输出

这几乎是最简单的嵌套调用表达,因为没有回调逻辑没有条件语句,想象一下,里面如果再有标记判断,switch之类的,会是何等壮观。

使用PromiseKit

代码如下

异步封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (AnyPromise *)sleep:(int)i {
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(arc4random()%2+1);
NSLog(@"%d", i);

if (i==4) {
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:100 userInfo:@{@"hhe":@"123"}];
resolve(error);

}else {
resolve();
}

});
}];
}

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
[self sleep:0].then(^(){
return [self sleep:1];
}).then(^(){
return [self sleep:2];
}).then(^(){
return [self sleep:3];
}).then(^(){
return [self sleep:4];
}).catch(^(){
NSLog(@"报错了");
}).always(^() {
NSLog(@"运行结束");
});

for 循环调用

1
2
3
4
5
6
7
8
9
10
11
12
13
AnyPromise *promise = [self sleep:0];
for (int i=1; i<6; i++) {
promise = promise.then(^(){
return [self sleep:1];
});
if(i==5) {
promise.catch(^(){
NSLog(@"报错了");
}).always(^() {
NSLog(@"运行结束");
});
}
}

补充

网上其它文章介绍所说的PMKPromiseFulfiller、PMKPromiseRejecter这两个block已经没有了,统一用PMKResolver处理,当参数为NSError或其子类时,就会由catch来处理,且剩余的promise不会继续执行。