我们知道,Promise是ECMAScript 6写入了标准的产物(比如IE11及以下版本就不支持),在ECMAScript 5标准中并不支持原生的Promise对象,网上可以找到各种第三方库来获得相应的支持。其实在通过对Promise原理的理解后,自己也可以写一个“类函数”来支持Promise。虽然我们在程序开发中不一定都要自己造轮子,但通过自己造轮子的过程可以加深对原生对象原理的认识和理解。
以下CustomPromise的原码是博主自己写的Promise“类函数”,支持原生Promise基本功能及与其相同的“then().then()...”链式调用,但对于【(原生Promise支持的)前一个Promise(在then中指定)的resolve或reject回调执行后,返回的值本身又是一个Promise对象】的情况没做考虑支持(说明:测试代码中使用的箭头函数“=>”属于ES6标准):
//var i=0;
function CustomPromise(fn){
//debugger;
//region 属性、方法
//this.name=++i; //for test
//this.fn=fn;
this.resolveTask=function(arg){ //arg:与原生Promise一样,回调中只能传递一个参数
//一个Promise对象中安排一次遇到的resolve或reject任务,先遇到谁就安排谁,后面的忽略
if (this.hasTaskHandler){
return;
}
this.hasTaskHandler=true;
var self=this;
setTimeout(function(){
if (self.state=='pending'){
if (!!self.resolve) {
try{
arg=arg||self.arg;
self.next.arg=self.resolve(arg); //执行的结果作为链中下一节点resolve回调的参数
self.state='resolved';
}catch(e){
//出错应该在链中传递rejected状态;如果链中都没有reject回调,则抛出异常:
self.findRejectInChain(e);
}
}
//else {
//调用了resolveTask,却没有指定resolve回调,与原生Promise一样不抛异常
//}
}else if (self.state=='rejected'){ //如果等待任务执行的过程出错改变了状态,进入这个条件只有一种情况:即是在链中前方节点执行了findRejectInChain()的过程中修改了此节点的state为“rejected”,这种情况即便没有指定self.reject回调也不应该(像rejectTask中一样)再执行findRejectInChain()方法。
debugger;
if (!!self.reject) {
try{
self.next.arg=self.reject(self.arg); //执行的结果作为链中下一节点resolve回调的参数
self.state='resolved';
}catch(e){
//出错应该在链中传递rejected状态;如果链中都没有reject回调,则抛出异常:
self.findRejectInChain(e);
}
}
}
},0);
};
this.rejectTask=function(arg){ //arg:与原生Promise一样,回调中只能传递一个参数
//一个Promise对象中安排一次遇到的resolve或reject任务,先遇到谁就安排谁,后面的忽略
if (this.hasTaskHandler){
return;
}
this.hasTaskHandler=true;
var self=this;
setTimeout(function(){
if (self.state!='resolved'){
if (!!self.reject){
try{
self.next.arg=self.reject(arg); //执行的结果作为链中下一节点resolve回调的参数
self.state='resolved';
}catch(e){
//出错应该在链中传递rejected状态;如果链中都没有reject回调,则抛出异常:
self.findRejectInChain(e);
}
}else { //if (!self.reject) 没有指定reject回调
if (self.state=='rejected' && (arg instanceof Error)) {
//出错应该在链中传递rejected状态;如果链中都没有reject回调,则抛出异常:
self.findRejectInChain(arg);
}else{
//调用了rejectTask,却没有指定reject
self.findRejectInChain(new Error('缺少reject回调'));
}
}
}
},0);
};
//在链中传递rejected状态;如果链中都没有reject回调,则抛出异常
this.findRejectInChain=function(e){
var chainProm=this.next;
var hasRejectInChain=false;
while (!!chainProm){
chainProm.state='rejected';//后面的链中都不执行resolve回调
chainProm.arg=e; //错误e作为后面reject回调的接收参数值
if (!!chainProm.reject){
hasRejectInChain=true;
break;
}
chainProm=chainProm.next;
}
if (!hasRejectInChain){
throw e;
}
};
this.then=function(res,rej){
if (!res && !rej){
return this; //传入空的传数时忽略,但不能断开链
}else {
this.resolve=res;
this.reject=rej;
//为了支持链式调用这里需要再返回一个新的CustomPromise:
var then_fn=function(then_resolve,then_reject){
then_resolve();
};
this.next= new CustomPromise(then_fn);
return this.next;
}
};
//endregion 属性
//初始化:
if (fn){
try{
this.state='pending';
fn(this.resolveTask.bind(this),this.rejectTask.bind(this));
}catch(e){
//如果fn执行出错了,且未有安排resolve或reject任务,那么需要调用rejectTask安排reject任务将异常传递下去
if (!this.hasTaskHandler){
this.state='rejected';
this.rejectTask.call(this,e);
}
}
}
}
//以下是针对CustomPromise各种情形的测试,结果可以与原生Promise作对比:
debugger;
//基本流程测试:
var myPromise=new CustomPromise(function(suc,fail){
var rand=Math.random();
console.log(rand);
if(rand>0.7) {
suc();
console.log('已安排任务1');
}else if (rand>0.3){
suc();
console.log('已安排任务1,即将出错,但在出错前已经安排任务或异常处理的情况下不再安排异常处理,因此即将进入的是任务1');
kkksfsf();
}else{
fail();
console.log('已安排处理异常1');
}
});
myPromise.then(()=>{console.log('成功执行任务1,任务1中将出错,进入异常2');ksdfs();},()=>{console.log('执行处理异常1,将进入任务2');})
.then(()=>{console.log('成功执行任务2,任务2中将出错,进入异常3');p.noAction();},()=>console.log('执行处理异常2,将进入任务3'))
.then(()=>console.log('成功执行任务3'),()=>console.log('执行处理异常3'));
//以下测试得到与原生Promise一致的结果:
//new CustomPromise((x,y)=>{fsf();x();}); //【测试先执行出错,没有指定resolve和reject】,返回Promise对象,并抛出异常fsf is not defined
//new CustomPromise((x,y)=>{fsf();x();}).then(()=>{}); //【测试先执行出错,没有指定reject】,返回Promise对象,并抛出异常fsf is not defined
//new CustomPromise((x,y)=>{fsf();x();}).then(()=>{},()=>{}); //【测试先执行出错,reject空函数处理】,返回Promise对象,无异常
//new CustomPromise((x,y)=>{fsf();x();}).then(()=>{}).then(()=>{}); //【测试先执行出错,链式中的节点都没有reject处理】,返回Promise对象,并抛出异常fsf is not defined
//new CustomPromise((x,y)=>{fsf();x();}).then(()=>{}).then(()=>{},()=>{}); //【测试先执行出错,链式中隔了几个节点后reject空函数处理】,返回Promise对象,无异常
//new CustomPromise((x,y)=>{fsf();x();}).then((p1,p2)=>{console.log(1);console.log(2);}).then(()=>{},(y)=>{console.log(3);}); //【测试先执行出错,链式中隔了几个节点后reject处理】,返回Promise对象,输出3
//new CustomPromise((x,y)=>{y();x();}).then((p1)=>{console.log(1);}).then(()=>{},(y)=>{console.log(3);}); //【测试先调用reject,链式中隔了几个节点后reject回调时处理】,输出3
//new CustomPromise((x,y)=>{xsfsf();y();}).then((p1)=>{console.log(1);}).then(()=>{}); //【测试先执行出错,但没有指定reject回调时的错误】,返回Promise对象,并抛出异常Uncaught ReferenceError: xsfsf is not defined
//new CustomPromise((x,y)=>{y();xsfsf();}).then((p1)=>{console.log(1);}).then(()=>{}); //【测试先调用reject,但没有指定reject回调时的错误】,返回Promise对象,并抛出异常Uncaught Error: 缺少reject回调
//new CustomPromise((x,y)=>{x();xsfsf();}).then().then((p1)=>{console.log(1);}).then(()=>{}); //【测试先安排resolve后面错误被忽略,以及then()传空参数维持链式操作】, 返回Promise对象,并输出1
//new CustomPromise((x,y)=>{x(23);xsfsf();}).then().then((p2)=>{console.log(p2);}).then(()=>{}); //【测试then()传空参数不中断链,并且将原来的参数往下传递参数直到有指定回调为止】,返回Promise对象,并输出23
//new CustomPromise((x,y)=>x(2)).then((p1,p2)=>sfdf()).then(()=>console.log('000')).then(()=>console.log(1),(p2)=>{console.log("1."+p2);return "2."+p2+' resolved';}).then((x)=>console.log(x));//【测试错误作为参数在链中传递给reject回调,reject执行后结果作为后面节点的resolve回调的参数】, 返回Promise对象,并输出1.ReferenceError: sfdf is not defined 2.ReferenceError: sfdf is not defined resolved
/*
//用原生Promise测试,对比结果:
var promise=new Promise(function(suc,fail){
var rand=Math.random();
console.log(rand);
if(rand>0.7) {
suc();
console.log('已安排任务1');
}else if (rand>0.3){
suc();
console.log('已安排任务1,即将出错,但在已经安排任务或异常处理的情况下不再安排异常处理,因此即将进入的是任务1');
kkksfsf();
}else{
fail();
console.log('已安排处理异常1');
}
});
promise.then(()=>{console.log('成功执行任务1,任务1中将出错,进入异常2');ksdfs();},()=>{console.log('执行处理异常1,将进入任务2');})
.then(()=>{console.log('成功执行任务2,任务2中将出错,进入异常3');p.noAction();},()=>console.log('执行处理异常2,将进入任务3'))
.then(()=>console.log('成功执行任务3'),()=>console.log('执行处理异常3'));
*/
以下测试结果也是与预料一致的,可以用原生Promise加以比较验证,得出的也是一致的流程结果。
注意说明
1. 作为基础实现,目前CustomPromise中的链式调用只支持处理【前一个Promise(在then中指定)的resolve或reject回调的返回值是普通对象】的情况,对于【(原生Promise支持的)前一个Promise(在then中指定)的resolve或reject回调执行后,返回的值本身又是一个Promise对象】的情况并没有做支持;
2. 以下原生Promise的静态/实例方法在CustomPromise中没做实现,原生Promise及其方法在各个浏览器的支持程度也并不相同,详细可参见浏览器兼容性中的表格说明:
Promise.all()
Promise.allSettled()
Promise.any()
Promise.prototype.catch()
Promise.prototype.finally()
Promise.race()
Promise.reject()
Promise.resolve()