开发步骤
我们每一步按照Promise/A+层层的规范编写Promise,以便理解Promise处理过程。
为了避免与浏览器中的Promise函数冲突,此次用Defer代替Promise:
function Defer(){
this.value = null;//代理的值
}
1.Promise 参数 executor
当新建一个promise需要用户传递一个executor函数,其形参包含Promise自行传递的resolve,reject两个方法,分别表示代理的值解析成功并传递值,代理的值解析失败并传失败原因。而如果executor在执行过程中出错,则promise立即被拒绝(调用reject),代码如下:
function Defer(executor){
if(!(this instanceof Defer)){
throw 'Defer is a constructor and should be called width "new" keyword';
}
if(typeof executor !== 'function'){
throw 'Defer params must be a function';
}
try{
executor.call(this, this.resolve.bind(this), this.reject.bind(this));//传递resolve,reject方法
}catch(e){//报错立即拒绝
this.reject(e);
}
}
Defer.prototype = {
constructor : Defer,
resolve : function(value){
this.value = value;//缓存代理的值
},
reject : function(reason){
this.rejectReason = reason;//缓存失败原因
}
}
2.Promise 状态
按照规范,Promise有三种状态:
pending: 初始状态,未完成或拒绝,可改变状态。
fulfilled(resolved): 操作成功完成,不可改变状态,拥有不可变的终值。
rejected: 操作失败,不可改变状态,拥有不可变的拒因。
为了记录当前Promise状态我们需要用一个属性缓存起来:
function Defer(executor){
this.status = 'pending';
...省略...
...省略...
}
而相应的resolve,reject方法内部要修改当前promise状态:
Defer.prototype = {
constructor : Defer,
resolve : function(value){
this.value = value;//缓存代理的值
this.status = 'resolved';
},
reject : function(reason){
this.rejectReason = reason;//缓存失败原因
this.status = 'rejected';
}
}
3.then(onFulfilled, onRejected)重要部分
3.1简要
promise/A+规范提出通过then方法访问当前Promise的代理值,并且可被同一个promise调用多次,最后函数返回promise对象,所以then函数:
...
then : function(onFulfilled, onRejected){
return this;
},
...
3.2then参数的调用时期和要求:(核心)
(1) onFulfilled(onResolved):可选参数,如果不是函数则必须忽略;
(2) onRejected:可选参数,如果不是函数则必须忽略;
(3) 当promise成功执行,所有onFulfilled按注册顺序执行,如果promise被拒绝,所有onRejected按注册顺序执行;
(4) onFulfilled 和 onRejected必须作为纯函数调用
(5) promise的executor执行完毕并调用resolve或reject方可调用then参数onFulfilled 和 onRejected。
(6) 无论promise状态是resolved还是rejected,只要还有未执行onFulfilled,onRejected或catch(只处理reject状态)存在且调用,返回的promise均为resolved状态;(该规则在下一节”triggerThen处理”和下下节”catch异常“会有注释,一看便知)
api调用示例
new promise().then(fn).then(fn).then(fn).....
谈谈规则(4),两个函数必须作为纯函数调用。所谓纯函数调用我认为是不包含在Object的属性当中并直接引用(否则this会指向该Object,可以通过apply,call,bind改变this指向),并且this的值是undefined(严格模式才会如此,非严格模式this指向window);则代码应是这样(示例):
onFulfilled.call(undefined, promise_value);
谈谈规则(5),等待executor执行完毕并调用resolve或reject方可调用then参数。我们知道executor内部很多情况下会有异步操作,而我们调用then方法与创建promise对象在同一个“执行上下文”当中的(从api调用示例可知),显然then方法不可能在创建promise对象之后立即执行其参数onFulfilled 或 onRejected,而是通过promise内部缓存存储onFulfilled 和 onRejected(一般为数组),当需要执行参数时候调用数组的shift方法则可按注册顺序执行,这样同时解决了规则(3),所以then方法任务是缓存参数,而规则(1)(2)只能下放到触发onFulfilled 或 onRejected时候才判断。所以整个过程就是executor执行完毕得到代理的值通过resolve或reject返回给then参数。
所以then的代码应该是这样写:
...
then : function (onFulfilled, onRejected){//只做缓存作用
this.thenCache.push({onFulfilled:onFulfilled,onRejected:onRejected});
return this;
},
...
而executor函数如果在promise里直接调会比then函数先执行,如果executor是同步操作,那么Promise的resolve或reject方法会在then前面执行,而then此时还没做好缓存onFulfilled 或 onRejected任务Promise就开始按顺序调用onFulfilled 或 onRejected必然会出错。为了让then先执行,Defer的代码应该是这样写:
function Defer(executor){
if(!(this instanceof Defer)){
throw 'constructor Defer should use "new" keyword';
}
if(typeof executor !== 'function'){
throw 'Defer params should be a function';
}
this.thenCache = [];//{resolve:,reject:}
this.status = 'pendding';
this.value = null;
this.rejectReason = null;//reject拒因
var self = this;
setTimeout(function(){//把executor的call任务插入到Event Loop的消息队列去,以异步执行executor,避免与then方法同步
try{
executor.call(self, self.resolve.bind(self), self.reject.bind(self));
}catch(e){
self.reject(e);
}
},0);
}
executor方法会有两个参数:resolve,reject,都是处理promise状态,并设置promise代理的value值;我们可以借用这两个方法来调用then参数,把(1)、(2)条规则判断下放到triggerThen处理代码如下:
...
resolve : function(value){
this.status = 'fulfilled';
this.value = value;//promise的值
this.triggerThen();//触发then参数
},
reject : function(reason){
this.status = 'rejected';
this.rejectReason = reason;//拒因
this.triggerThen();
},
then : function (onFulfilled, onRejected){
this.thenCache.push({onFulfilled:onFulfilled,onRejected:onRejected});
return this;
},
triggerThen : function(){
...
}
...
3.5 triggerThen 处理
综合上一小节的理论阐述,triggerThen直接贴出代码,旁边会加上注释说明属于哪种规则
Defer.prototype.triggerThen = function(){
var current = this.thenCache.shift();//规则(3)
var res = null;
if(!current){//成功解析并读取完then cache
return this;
}
if(this.status === 'resolved'){
res = current.resolve;
}else if(this.status === 'rejected'){
res = current.reject;
}
if(typeof res === 'function'){//规则(1)(2)
this.value = res.call(undefined, this.value);//重置promise的value,规则(4)
this.status = 'resolved';//规则(6),只要有处理,则状态为resolved
this.triggerThen();//继续执行then链
}else{//不是函数则忽略
this.triggerThen();//规则(1)(2)
}
};
4 异常处理
当promise在处理过程中出现问题,可能是代码出错,可能是throw抛出了异常,并且抛出异常之后promise状态为rejected,如果用户提供catch处理,则把promise状态更改为resolved,其处理的方式和then一样,缓存异常处理函数:
...
Defer.prototype.catch = function(fn){
if(typeof fn === 'function'){
this.errorHandle = fn;
}
};
...
triggerThen处理异常代码如下:
Defer.prototype.triggerThen = function(){
var current = this.thenCache.shift();
var res = null;
if(!current && this.status === 'resolved'){//成功解析并读取完then cache
return this;
}else if(!current && this.status === 'rejected'){//解析失败并读取完then cache,直接调用errorHandle
if(this.errorHandle){
this.value = this.errorHandle.call(undefined, this.rejectReason);
this.status= 'resolved';//规则(6)
}
return this;
};
if(this.status === 'resolved'){
res = current.resolve;
}else if(this.status === 'rejected'){
res = current.reject;
}
if(typeof res === 'function'){
try{
this.value = res.call(undefined, this.value || this.rejectReason);//重置promise的value
this.status = 'resolved';
this.triggerThen();//继续执行then链
}catch(e){
this.status = 'rejected';//异常,则promise为reject,规则(6)
this.rejectReason = e;
return this.triggerThen();//触发then链
}
}else{//不是函数则忽略
this.triggerThen();
}
};
4 测试
function test(){
return new Defer(function(res,rej){
setTimeout(function(){
res(1);
},1000);
});
}
test().then(function(value){
console.log('resolve then 1', value);
return 1;
}).then(function(value){
console.log('resolve then 2', value);
throw 2;
}).catch(function(e){
console.log('error',e);
});
//结果:
//resolve then 1 1
//resolve then 2 1
//error 2
function test2(){
return new Defer(function(res,rej){
setTimeout(function(){
rej(1);
},1000);
});
}
test2().then(null, function(value){
console.log('reject then 1', value);
throw 'error 1'
}).then(null, function(value){
console.log('reject then 2', value);
throw 'error 2';
}).catch(function(e){
console.log('error',e);
});
//结果:
//reject then 1 1
//reject then 2 error 2
//error erro 2
test2().then(null, function(value){
console.log('reject then 1', value);
throw 'throw error from then 1';
}).then(function(value){
console.log('resolve then 2', value);
}).catch(function(e){
console.log('error',e);
});
//结果:
//reject then 1 1
//error throw error from then 1
5 小结
一个简单版的Promise就大功告成,可能本文对Promise/A+规范描述还不够详细,还有其他理论并没有过多描述,甚至有些理论有出入(比如then返回的本是新的Promise对象),请大家多多包涵;本次是简化了一些,以练习为主,学习Promise概念的核心思想。对于理解本次Promise代码,最关键还是需要好好理解浏览器的事件循环(Event loop),一句话即Promise是先同步处理then、catch函数再异步处理executor函数,接着通过resolve或reject触发then、catch的参数。
另外笔者埋了一个坑,Defer对象在resolve或reject函数调用之后已成settled状态(rejected 或 rejected),此时状态不能改变,也就为什么then每次返回的是一个新的promise,这样可返回不同状态的promise。而本次代码Defer本身自带了resolve和reject函数,是随时可改变自身状态,并且then仍是返回当前promise,大家可以想像一下如何改造实现。可以参考Jquery的Deffered或者GitHub上的Q模块,两个实现的思路是一样。
有问题的请指正~~
完整的源码点击这里:github.com/humyfred/js…
参考文献
MDN:Promise