我們知道,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()