天天看點

自己寫一個Promise

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

以下測試結果也是與預料一緻的,可以用原生Promise加以比較驗證,得出的也是一緻的流程結果。

自己寫一個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()

繼續閱讀