Promise让代码变得更人性化
曾经我一直在思考,为什么代码会比较难读。后来发现,我们平时要阅读的所有媒体:报纸、书、新闻,我们在阅读的时候,都是从上到下一直读下去的,然而,我们的在读代码的时候,经常要跳着去读,这种阅读方式其实是反人类的,如果我们能在读代码的时候,也可以从上往下一直读下去,那么,代码就会变得可读性提高很多。
对比JS中,callback是让我们跳来跳去读代码最大的罪魁祸首,它让我们的流程变得混乱,Promise正是为了解决这一问题而产生的。我们来对比一下这个过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | varrender=function(data){ }; vargetData=function(callback){ $.ajax({ success:function(){ callback(data); } }); }; varinit=function(){ getData(function(data){ render(data); getData(function(data){ render(data); }); }); }; init(); |
使用Promise之后
| varinit=function(){ getData({ }).then(function(data){ render(data); returngetData({}); }).then(function(data){ render(data); }); }; |
很明显看出,代码就变成线性的了,逻辑也变得更加清晰可读
Promise流程再优化
promse出来之后,大家都有很多的想法,在Promise之上再封装一层,使用异步流程更清晰可读。下面是Abstract-fence 2.0(开发中)的一种解决方案(规范)
Abstract-fence中,function会被分解为多个task
| Model.task('getData',function(scope,next,{$,util}){ $.ajax({ success:function(data){ next(); } }); }); Model.task('render',['getData'],function(scope){ vardata=scope.data; // 使用data进行渲染 }); Model.task('init',[render].then(render)); Model.runWorkflow(init); |
其中, init是task render执行后再执行render, 而render任务又是由getData任务执行后再渲染组成,其中每个task的定义function的参数使用依赖注入传递,全局属性使用{}包裹
但是在使用Promise.all的过程中,遇到了一个Promise奇怪的问题
Array.prototype.then与Promise.all
很简单的一段代码
| varp=newPromise(function(rs,rj){ setTimeout(rs,1000); }); Promise.all([p]).then(function(){ console.log(2); }); console.log(1); |
毫无疑问,这段代码在浏览器运行会先打印1,然后再输出2 但如果在前面增加对then方法的定义,如下代码
| Array.prototype.then=function(){ }; varp=newPromise(function(rs,rj){ setTimeout(rs,1000); }); Promise.all([p]).then(function(){ console.log(2); }); console.log(1); |
那么这段代码只会打印出1, 2却永远不会运行
查了很多资料,确认promise.all的参数只能接收数组(类数组)
比如如下代码就会报错
| varp=newPromise(function(rs){}); Promise.all(p);// 报错 Promise.all([p]);// ok |
所以,Promise.all接收一个Iterator可遍历对象
对数组的prototype.then定义为什么会影响到Promise的行为呢?
Promise A+规范
Promise A+规范看起来还是有点绕,这里省略掉一些具体的实现细节,将Promise A+更直白的阐述如下
1. Promise then方法需要return一个新的Promise出来,如下
| .then=function(rsFunc,rjFunc){ varpromise2=newPromise(); returnpromise2; }; |
2. 如果promise本身状态变更到fulfilled之后,调用rsFunc,rsFunc的解析值x, 与新的promise2进行promise的解析过程[[Resolve]](promise2, x), x的取值不同,有不同的情况
3. 若x为一个promise,则x完成的最后,再fufill promise2, 对应如下代码
| newPromise(function(rs,rj){ rs(); }).then(function(data){ // 对应于x的返回值 returnnewPromise(rs,rj){ }); }); |
4. 若x为一个对象或者函数,如果有then方法,将会执行then方法,then方法this指向x本身,如下
| newPromise(function(rs,rj){ rs(); }).then(function(data){ // 对应于x的返回值 return{ a:1, then:function(rs,rj){ console.log(this.a); rs({a:2}); } }; }).then(function(data){ console.log(data.a); }); |
5. 如果x没有then方法,那么,x将会做为值来 满足 promise2
| newPromise(function(rs,rj){ rs(); }).then(function(data){ // 对应于x的返回值 return{ a:1 }; }).then(function(data){ console.log(data.a); }); |
Promise A+给出了一些具体的执行细节,保证了then的顺序执行,但在规范中,并未提到Promise.all方法的执行方式
为此,查看bluebird的Promise.all实现方法
BlueBird关于Promise.all实现方法解析
首先,promise中引用promise_array代码如下(已略去一些无关代码)
| varINTERNAL=function(){}; varapiRejection=function(msg){ returnPromise.reject(newTypeError(msg)); }; functionProxyable(){} vartryConvertToPromise=require("./thenables")(Promise,INTERNAL); varPromiseArray= require("./promise_array")(Promise,INTERNAL, tryConvertToPromise,apiRejection,Proxyable); |
promise.all的实现也很简单
| Promise.all=function(promises){ returnnewPromiseArray(promises).promise(); }; |
可见,具体的细节在promise_array中的实现
| functionPromiseArray(values){ varpromise=this._promise=newPromise(INTERNAL); if(values instanceofPromise){ promise._propagateFrom(values,3); } promise._setOnCancel(this); this._values=values; this._length=0; this._totalResolved=0; // 初始化 this._init(undefined,-2); } |
PromiseArray的构造方法中,将参数赋值给this._values,待_init方法中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | PromiseArray.prototype._init=functioninit(_,resolveValueIfEmpty){ varvalues=tryConvertToPromise(this._values,this._promise); // 如果values可以转化为promise对象,那么根据不同的状态,会提前return if(values instanceofPromise){ values=values._target(); varbitField=values._bitField; ; this._values=values; // 这个状态是pending的状态 if(((bitField&50397184)===0)){ this._promise._setAsyncGuaranteed(); // Promise,将会等Promise对象本身状态改变后再次 returnvalues._then( init, this._reject, undefined, this, resolveValueIfEmpty ); // FULFILLED, 并没有提前return, 继续进行 }elseif(((bitField&33554432)!==0)){ values=values._value(); // rejected的状态,提前终止 }elseif(((bitField&16777216)!==0)){ returnthis._reject(values._reason()); }else{ returnthis._cancel(); } } values=util.asArray(values); if(values===null){ varerr=apiRejection( "expecting an array or an iterable object but got "+util.classString(values)).reason(); this._promise._rejectCallback(err,false); return; } if(values.length===0){ if(resolveValueIfEmpty===-5){ this._resolveEmptyArray(); } else{ this._resolve(toResolutionValue(resolveValueIfEmpty)); } return; } this._iterate(values); }; |
init总结为几步
1.尝试转换参数为Promise对象
2.如果转换成功,那么检查Promise对象的状态
1. Pending,等待Promise
2. fulfilled, 换取返回值,继续进行
3. Rejected 终止,返回原因
4. 其他, 终止
上面的代码可以看出,一旦数组的具有then方法,就可被tryConvertToPromise方法转换为一个Promise对象,如果then方法未实现promise规范,那么Promise对象就会处于Pending的状态,Promise.all方法永远就不会达到fulfilled的条件,问题也就明白了
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习