谈一谈Promise

本文将会向你简单介绍promise,并在最后尝试使用OC实现一个可用的promise库。

什么是promise?

在面向对象的世界promise也并不特殊。promise对象表示异步操作的最终完成(或失败)及其结果值。

这样一句话显然无法让人理解promise,它是做什么的?要怎么使用它?为什么要使用它?下面的内容将会解释这些问题。即使现在完全不理解promise是什么,请先记住“promise”这个字面含义给你带来的暗示:某件事情(代码)在未来的某个时刻发生(执行)。

我们遇到的问题

通常我们使用block回调来处理一些异步操作,比如:

[object doSomethingWithArg:arg handler:^(id data){  
    //resolve data
}];
复制代码

上面的代码没有问题,然而假设我们遇到了如下场景:发起网络请求A>获取网络请求A的返回数据A>使用返回数据A作为参数发起网络请求B…如此往复,通常还包含着网络错误的回调,代码看起来是这样的:

//ViewController.m
- (void)viewDidLoad {
    [[XXNetwork shared] requestAWithArg:arg success:^(id dataA){  
        [[XXNetwork shared] requestBWithArg:dataA success:^(id dataB){  
            [[XXNetwork shared] requestCWithArg:dataB success:^(id dataC){  
                    //resolve dataC
            } failure:^(NSError *error){
                //网络错误处理
            }];
        } failure:^(NSError *error){
            //网络错误处理
        }];
    } failure:^(NSError *error){
        //网络错误处理
    }];
}

//XXNetwork.h
//异步的网络请求
- (void)requestAWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;
- (void)requestBWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;
- (void)requestCWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;


//XXNetwork.m
//原有的方法
- (void)requestAWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure {
	//doSomething
}
...
复制代码

上面代码看起来还算友好,但是当这样的嵌套太深的时候,问题就出现了:

1.过多的嵌套造成代码无法被轻松的阅读

2.网络错误的状况没有统一的处理

这样场景下使用block回调嵌套显然不够优雅,那么我们要怎么做?

使用promise改造代码

对于我们遇到的问题,promise将会大显身手,使用promise改造后代码看起来大概是这样的:

//ViewController.m
- (void)viewDidLoad {
    [[XXNetwork shared] requestAWithArg:arg].then(^id(id value){
        //value 即 dataA
        return [[XXNetwork shared] requestBWithArg:value];
    }).then(^id(id value){
        //value 即 dataB
        return [[XXNetwork shared] requestCWithArg:value];
    }).then(^id(id value){
        //value 即 dataC
        //resolve dataC
    }).catch(^id(NSError * error){
        //网络错误处理
    });
}



//XXNetwork.h
//原有的方法
//异步的网络请求
- (void)requestAWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;
- (void)requestBWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;
- (void)requestCWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;


//包装后的方法
- (Promise *)requestAWithArg:(id)arg;
- (Promise *)requestBWithArg:(id)arg;
- (Promise *)requestCWithArg:(id)arg;



//XXNetwork.m
//原有的方法
- (void)requestAWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure {
	//doSomething
}
...
//包装后的方法
- (Promise *)requestAWithArg:(id)arg {
    Promise *p = [Promise new];
    [[XXNetwork shared] requestAWithArg:arg success:^(id dataA){
        p.fulfill(dataA);
    } failure:^(NSError *error){
    	p.reject(error);
    }];
    return p;
}
...
复制代码

viewDidLoad中的代码表示的是:发起网络请求A,然后(then)使用返回数据A作为参数发起网络请求B,然后(then)使用返回数据B作为参数发起网络请求C,然后(then)处理dataC。catch则会处理链上产生的错误。你会发现,这段代码阅读下来非常贴近日常的语言习惯,可怕的回调地狱不见了,网络错误有地方做统一处理,太酷了对不对?更重要的是我们只需要做一些简单的改造或包装。

promise是怎么运作的

在感叹promise的优雅之后,我们产生这些疑问:then是什么?catch是什么?value从哪里来的?为什么需要返回一个promise对象?promise对象的fulfill和reject方法是干什么的?

为了方便理解,先将viewDidLoad中的代码缩减一部分,其他不变,然后看一下执行过程

//ViewController.m
- (void)viewDidLoad {
    [[XXNetwork shared] requestAWithArg:arg].then(^id(id value){
        //value 即 dataA
    });
}

//XXNetwork.h
//原有的方法
//异步的网络请求
- (void)requestAWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;
- (void)requestBWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;
- (void)requestCWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure;


//包装后的方法
- (Promise *)requestAWithArg:(id)arg;
- (Promise *)requestBWithArg:(id)arg;
- (Promise *)requestCWithArg:(id)arg;



//XXNetwork.m
//原有的方法
- (void)requestAWithArg:(id)arg success:(void (^)(id data))success failure:(void (^)(NSError *error))failure {
	//doSomething
}
...
//包装后的方法
- (Promise *)requestAWithArg:(id)arg {
    Promise *p = [Promise new];
    [[XXNetwork shared] requestAWithArg:arg success:^(id dataA){
        p.fulfill(dataA);
    } failure:^(NSError *error){
    	p.reject(error);
    }];
    return p;
}
...
复制代码

1.在viewDidLoad执行后,执行XXNetwork单例requestAWithArg:方法

2.在requestAWithArg:方法内创建了一个promise对象。调用原有的方法requestAWithArg:success:failure:,如果异步请求成功则将会调用promise的fulfill方法,失败则将会调用reject方法。返回这个promise对象

3.调用promise的then方法,将成功后的需要执行的block加入到promise中

4.最后当requestAWithArg:success:failure:进入成功回调则调用promise的fulfill方法,使用promise的then方法加入的block会执行。同理当requestAWithArg:success:failure:进入失败回调则调用promise的reject方法,使用promise的catch方法加入的block就会执行

回想一开始我们对promise对象的描述:promise对象表示异步操作的最终完成(或失败)及其结果值。现在脑海中有一些轮廓正在出现,让我们结合下面图将它梳理清晰:

promise对象始终处于以下3个状态之一:

  • pending初始化状态

  • fulfilled表示操作已经完成

  • rejected表示操作已经失败

当我们创建一个promise对象时,promise处于pending状态;我们可以通过promise的then,catch等方法将成功或失败后需要执行的任务(block)加入到promise的“回调列表”;当异步操作完成后调用promise的fulfill或reject方法,并传递参数;promise的状态从pending转换到fulfilled或rejected,这样的转换是不可逆的,同时会调用之前使用的then或者catch加入到该promise任务(block);then或者catch方法将会返回一个新的promise对象,我们可以对新的promise继续调用then或catch方法,从而形成了链式调用。这就是promise的核心部分。

目前为止仅简单介绍了promise的一部分,不再做更多的使用介绍,通过以下网址可以获取更多的关于promise的规范和使用方法

developer.mozilla.org/en-US/docs/…

promisesaplus.com/

在代码中使用promise之前

Promise并非OC原生提供,使用PromiseKit是一个好的选择,它有丰富可靠的API,你可以在这里找到它,具体的使用方法参考其文档。

使用promise让我们远离了回调地狱,但是我们可以思考下一些问题要如何应对:失去了参数的类型信息要如何处理?promise是否有性能问题?能否中止一个promise的链?引入promise的学习成本有多少等等。这些会问题可以在实践中解开,找到适合使用的场景,做好权衡。

实现一个可用promise库

在了解一些规范后我们可以尝试自己实现一个可用的库ToyPromise,你会看到then,catch,finally,race,all这些熟悉的API的具体实现。由于一些原因使用上和promise的规范有一些差异,但核心的部分是不变的。ToyPromise目前仅是个玩具,欢迎贡献代码让它成长。

猜你喜欢

转载自juejin.im/post/5c67e6305188252e4662569c