1. 程式人生 > >ES6 Promise用法小結

ES6 Promise用法小結

1.什麼是Promise

Promise 是非同步程式設計的一種解決方案,其實是一個建構函式,自己身上有all、reject、resolve這幾個方法,原型上有then、catch等方法。

Promise物件有以下兩個特點。

(1)物件的狀態不受外界影響。Promise物件代表一個非同步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。

(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise

物件的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise物件添加回調函式,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

下面先 new一個Promise

let p = new Promise(function(resolve, reject){
		//做一些非同步操作
		setTimeout(function(){
			console.log('執行完成Promise');
			resolve('要返回的資料可以任何資料例如介面返回資料');
		}, 2000);
	});

重新整理頁面會發現控制檯直接打出

其執行過程是:執行了一個非同步操作,也就是setTimeout,2秒後,輸出“執行完成”,並且呼叫resolve方法。

注意!我只是new了一個物件,並沒有呼叫它,我們傳進去的函式就已經執行了,這是需要注意的一個細節。所以我們用Promise的時候一般是包在一個函式中,在需要的時候去執行這個函式,如:

<div onClick={promiseClick}>開始非同步請求</div>

const promiseClick =()=>{
	 console.log('點選方法被呼叫')
	 let p = new Promise(function(resolve, reject){
		//做一些非同步操作
		setTimeout(function(){
				console.log('執行完成Promise');
				resolve('要返回的資料可以任何資料例如介面返回資料');
			}, 2000);
		});
        return p
	}

重新整理頁面的時候是沒有任何反映的,但是點選後控制檯打出

當放在函式裡面的時候只有呼叫的時候才會被執行

那麼,接下里解決兩個問題:

1、為什麼要放在函式裡面

2、resolve是個什麼鬼

我們包裝好的函式最後,會return出Promise物件,也就是說,執行這個函式我們得到了一個Promise物件。接下來就可以用Promise物件上有then、catch方法了,這就是Promise的強大之處了,看下面的程式碼:

promiseClick().then(function(data){
    console.log(data);
    //後面可以用傳過來的資料做些其他操作
    //......
});

這樣控制檯輸出

先是方法被呼叫起床執行了promise,最後執行了promise的then方法,then方法是一個函式接受一個引數是接受resolve返回的資料這事就輸出了‘要返回的資料可以任何資料例如介面返回資料’

這時候你應該有所領悟了,原來then裡面的函式就跟我們平時的回撥函式一個意思,能夠在promiseClick這個非同步任務執行完成之後被執行。這就是Promise的作用了,簡單來講,就是能把原來的回撥寫法分離出來,在非同步操作執行完後,用鏈式呼叫的方式執行回撥函式。

你可能會覺得在這個和寫一個回撥函式沒有什麼區別;那麼,如果有多層回撥該怎麼辦?如果callback也是一個非同步操作,而且執行完後也需要有相應的回撥函式,該怎麼辦呢?總不能再定義一個callback2,然後給callback傳進去吧。而Promise的優勢在於,可以在then方法中繼續寫Promise物件並返回,然後繼續呼叫then來進行回撥操作。

所以:精髓在於:Promise只是能夠簡化層層回撥的寫法,而實質上,Promise的精髓是“狀態”,用維護狀態、傳遞狀態的方式來使得回撥函式能夠及時呼叫,它比傳遞callback函式要簡單、靈活的多。所以使用Promise的正確場景是這樣的:

promiseClick()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return runAsync3();
})
.then(function(data){
    console.log(data);
});

這樣能夠按順序,每隔兩秒輸出每個非同步回撥中的內容,在runAsync2中傳給resolve的資料,能在接下來的then方法中拿到。

(Ps:此處執行多次是因為研究該用法的時候我在一個react的demo中進行的,該頁面多個元素改變導致頁面多次渲染執行所致,正常頁面只渲染一次的話就所有隻會執行一次)

reject的用法

以上是對promise的resolve用法進行了解釋,相當於resolve是對promise成功時候的回撥,它把promise的狀態修改為

fullfiled,那麼,reject就是失敗的時候的回撥,他把promise的狀態修改為rejected,這樣我們在then中就能捕捉到,然後執行“失敗”情況的回撥。

function promiseClick(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的隨機數
				console.log('隨機數生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('數字太於10了即將執行失敗回撥');
				}
			}, 2000);
		   })
		   return p
	   }

	promiseClick().then(
		function(data){
			console.log('resolved成功回撥');
			console.log('成功回撥接受的值:',data);
		}, 
		function(reason, data){
			console.log('rejected失敗回撥');
			console.log('失敗執行回撥丟擲失敗原因:',reason);
		}
	);	

執行結果:

(PS:此處也是執行多次所以輸出多次,執行多次的原因和上次原因一致)

以上程式碼:呼叫promiseClick方法執行,2秒後獲取到一個隨機數,如果小於10,我們算成功,呼叫resolve修改Promise的狀態為fullfiled。否則我們認為是“失敗”了,呼叫reject並傳遞一個引數,作為失敗的原因。並將狀態改成rejected

執行promiseClick並且在then中傳了兩個引數,這兩個引數分別是兩個函式,then方法可以接受兩個引數,第一個對應resolve的回撥,第二個對應reject的回撥。(也就是說then方法中接受兩個回撥,一個成功的回撥函式,一個失敗的回撥函式,並且能在回撥函式中拿到成功的資料和失敗的原因),所以我們能夠分別拿到成功和失敗傳過來的資料就有以上的執行結果

catch的用法

與Promise物件方法then方法並行的一個方法就是catch,與try  catch類似,catch就是用來捕獲異常的,也就是和then方法中接受的第二引數rejected的回撥是一樣的,如下:

function promiseClick(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的隨機數
				console.log('隨機數生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('數字太於10了即將執行失敗回撥');
				}
			}, 2000);
		   })
		   return p
	   }

	promiseClick().then(
		function(data){
			console.log('resolved成功回撥');
			console.log('成功回撥接受的值:',data);
		}
	)
	.catch(function(reason, data){
		console.log('catch到rejected失敗回撥');
		console.log('catch失敗執行回撥丟擲失敗原因:',reason);
	});	

執行結果:

效果和寫在then的第二個引數裡面一樣。它將大於10的情況下的失敗回撥的原因輸出,但是,它還有另外一個作用:在執行resolve的回撥(也就是上面then中的第一個引數)時,如果丟擲異常了(程式碼出錯了),那麼並不會報錯卡死js,而是會進到這個catch方法中。如下:

function promiseClick(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的隨機數
				console.log('隨機數生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('數字太於10了即將執行失敗回撥');
				}
			}, 2000);
		   })
		   return p
	   }

	promiseClick().then(
		function(data){
			console.log('resolved成功回撥');
			console.log('成功回撥接受的值:',data);
			console.log(noData);
		}
	)
	.catch(function(reason, data){
		console.log('catch到rejected失敗回撥');
		console.log('catch失敗執行回撥丟擲失敗原因:',reason);
	});	

執行結果:

在resolve的回撥中,我們console.log(noData);而noData這個變數是沒有被定義的。如果我們不用Promise,程式碼執行到這裡就直接在控制檯報錯了,不往下運行了。但是在這裡,會得到上圖的結果,也就是說進到catch方法裡面去了,而且把錯誤原因傳到了reason引數中。即便是有錯誤的程式碼也不會報錯了

all的用法

與then同級的另一個方法,all方法,該方法提供了並行執行非同步操作的能力,並且在所有非同步操作執行完後並且執行結果都是成功的時候才執行回撥。

將上述方法複製兩份並重命名promiseClick3(), promiseClick2(), promiseClick1(),如下

function promiseClick1(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的隨機數
				console.log('隨機數生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('數字太於10了即將執行失敗回撥');
				}
			}, 2000);
		   })
		   return p
	   }
	   function promiseClick2(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的隨機數
				console.log('隨機數生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('數字太於10了即將執行失敗回撥');
				}
			}, 2000);
		   })
		   return p
	   }
	   function promiseClick3(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的隨機數
				console.log('隨機數生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('數字太於10了即將執行失敗回撥');
				}
			}, 2000);
		   })
		   return p
	   }

	Promise
		.all([promiseClick3(), promiseClick2(), promiseClick1()])
		.then(function(results){
			console.log(results);
		});

Promise.all來執行,all接收一個數組引數,這組引數為需要執行非同步操作的所有方法,裡面的值最終都算返回Promise物件。這樣,三個非同步操作的並行執行的,等到它們都執行完後才會進到then裡面。那麼,三個非同步操作返回的資料哪裡去了呢?都在then裡面,all會把所有非同步操作的結果放進一個數組中傳給then,然後再執行then方法的成功回撥將結果接收,結果如下:(分別執行得到結果,all統一執行完三個函式並將值存在一個數組裡面返回給then進行回撥輸出):

這樣以後就可以用all並行執行多個非同步操作,並且在一個回撥中處理所有的返回資料,比如你需要提前準備好所有資料才渲染頁面的時候就可以使用all,執行多個非同步操作將所有的資料處理好,再去渲染

race的用法

all是等所有的非同步操作都執行完了再執行then方法,那麼race方法就是相反的,誰先執行完成就先執行回撥。

我們將上面的方法延遲分別改成234秒

function promiseClick1(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的隨機數
				console.log('2s隨機數生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('2s數字太於10了即將執行失敗回撥');
				}
			}, 2000);
		   })
		   return p
	   }
	   function promiseClick2(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的隨機數
				console.log('3s隨機數生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('3s數字太於10了即將執行失敗回撥');
				}
			}, 3000);
		   })
		   return p
	   }
	   function promiseClick3(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的隨機數
				console.log('4s隨機數生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('4s數字太於10了即將執行失敗回撥');
				}
			}, 4000);
		   })
		   return p
	   }

	Promise
		.race([promiseClick3(), promiseClick2(), promiseClick1()])
		.then(function(results){
			console.log(results);
		},function(reason){
			console.log(reason);
		});

當2s後promiseClick1執行完成後就已經進入到了then裡面回撥,在then裡面的回撥開始執行時,promiseClick2()和promiseClick3()並沒有停止,仍舊再執行。於是再過3秒後,輸出了他們各自的回撥值

race的使用比如可以使用在一個請求在10s內請求成功的話就走then方法,如果10s內沒有請求成功的話進入reject回撥執行另一個操作。