本篇文章试图用尽量少的尽量直白的话帮助理解Promise是什么以及怎么用
Promise是一种特殊的值,用来保存异步计算所产生的结果,它的存在帮助我们用接近同步编程的思维方式和正常的流程方式处理异步计算带来的流程问题。在普通的程序流程中,如果存在一个这样的计算:
let {param1,param2} = someValue;const result1 = SyncFunc1(param1,param2);const result2 = SyncFunc2(result1);console.log(result2);//我们可以拿着result2到处去用
这样的代码符合计算顺序且符合人的正常思维。无论是写代码的人还是review代码的人都没有困扰。
但是如果在某个流程中涉及了异步的操作,比如网络请求,数据库请求(大部分都是这两种情况),鉴于JavaScript单线程,异步操作的特性和编程的普遍约定,以前要处理异步操作的情况就是回掉嵌套:
let {param1,param2} = someValue;asyncFunc1(param1,param2,(err,result1)=>{ if(!err){ asyncFunc2(result1,(err,result2)=>{ if(!err){ console.log(result2)//我们不能拿着result2到处去用,只能把所有用到result2的代码嵌套在这 } }) }});
这样的编程方式对于写Js时间比较久的人来说可能比较习惯,但对于使用非JS语言的后端程序员来说可能就觉得别扭。同时回调嵌套也存在着其它明显的问题:
- 增加了流程控制的难度和代码的可读性。使用回调嵌套只要涉及了异步操作,所有基于异步结果的操作都要嵌套到异步操作的回调函数内部,当异步操作的层级变多或出现交叉时,会导致代码写的很复杂,而业务上讲并不需要这么复杂。
- 控制反转的问题。异步操作本来只是整个流程中的一小步,现在基于回调嵌套的写法,它却成了之后所有步骤的祖先,之后所有步骤都成了它的一个小步骤(回调函数),而且考虑到使用第三方的异步代码的情况,如果第三方代码里面不正确的使用了传入的回掉函数,那整个流程会毁于这个第三方的异步操作,这无疑是很荒唐的。
基于这样的情况,ES6增加了原生的ES对象Promise,试图让一切不正常恢复正常。或许Promise给人感觉难以理解,但如果拿事件监听来与之对比,可能会好理解的多。先看用Promise怎么样改写上述异步代码:
let {param1,param2} = someValue;const p1 = new Promise((resolve,reject)=>{ asyncFunc1(param1,param2,(err,result1)=>{ if(!err){ resolve(result1) }else{ reject(err) } })})const p2 = p1.then( result1=>{ asyncFunc2(result2,(err,result2)=>{ if(!err){ return result2 }else{ return err } }) }, err=>{ console.log("error in asyncFunc1") })//这里我们就可以拿着p2到处去用了
通过上述代码和之前两段代码相比较,希望我大致说清楚了Promise的好处是什么。虽然使用Promise再语法上也是有些许的别扭,但至少我们可以『拿着结果到处去用了』,不是嘛。
如果用事件监听来理解Promise,或许容易一些:
asyncFunc(x){ //异步操作x并返回结果 return listener}const evt = asyncFunc( 8 )evt.on("complete",(result)=>{ //处理结果})evt.on("fail",(error)=>{ //处理错误})//evt这个监听器我们也可拿着到处去用
对于使用过事件监听器的人来说,这样的对比对理解Promise来说应该容易了一些。虽然Promise可以这样理解,但其实现机制还是与事件监听不一样的,使用方式也有一些差别。