Promise入門

衆所周知的,Javascript是一種單線程的語言,全部的代碼必須按照所謂的「自上而下」的順序來執行。本特性帶來的問題就是,一些未來的、未知的操做,必須異步實現(關於異步,我會在另外一篇文章裏進行討論)。本文將討論一個比較常見的異步解決方案——Promise,時至本文最後更新的日子,Promise的應用已經極其普遍。javascript

Promise解決的問題

我相信每一個前端都遇到過這樣一個問題,當一個異步任務的執行須要依賴另外一個異步任務的結果時,咱們通常會將兩個異步任務嵌套起來,這種狀況發生一兩次還能夠忍,可是發生不少次以後,你的代碼就會變成這個熊樣:前端

async1(function(){
		async2(function(){
			async3(function( async4(funciton(){
					async5(function(){
						//(╯°□°)╯︵┻━┻
						//...
					});
				});
			));	
		});
	});
複製代碼

這就是所謂的回調地獄,代碼層層嵌套,環環相扣,很明顯,邏輯稍微複雜一些,這樣的程序就會變得難以維護。java

對於這種狀況,程序員們想了不少解決方案(好比將代碼模塊化),但流程控制上,仍是沒有掏出})的大量嵌套。但去年ES2015的標準裏,Promise的標準化,必定程度上解決了JavaScript的流程操做問題。webpack

Promise的基本用法

時至今日,不少現代瀏覽器都已經實現,可是爲了兼容,建議自行對Promise進行封裝或者使用第三方的解決方案(如webpack對es6語法進行編譯)。 那麼,我麼將獲得一個Promise構造函數,新建一個Promise的實例:git

var _promise = new Promise(function(resolve, reject){
		setTimeout(function(){
			var rand = Math.random();
			if(rand<0.5){
				resolve("resolve" + rand);
			}else{
				reject("reject" + rand);
			}
		},1000);
		
	});
	
	/*運行結果: *有兩種狀況: *1)無事發生 *2)報錯形如:d.js:7 Uncaught (in promise) reject0.9541820247347901 */
	
複製代碼

由上所示,Promise的構造函數接收一個函數做爲參數,該函數接受兩個額外的函數,resolve和reject,這兩個函數分別表明將當前Promise置爲fulfilled(解決)和rejected(拒絕)兩個狀態。Promise正是經過這兩個狀態來控制異步操做的結果。接下來咱們將討論Promise的用法,實際上Promise上的實例_promise是一個對象,不是一個函數。在聲明的時候,Promise傳遞的參數函數會當即執行,所以Promise使用的正確姿式是在其外層再包裹一層函數。程序員

var run = function(){
		var _promise = new Promise(function(resolve, reject){
			setTimeout(function(){
				var rand = Math.random();
				if(rand<0.5){
					resolve("resolve" + rand);
				}else{
					reject("reject" + rand);
				}
			},1000);
		});
		return _promise;
	}
	run();
複製代碼

這是Promise的正經常使用法,接下來,就是對異步操做結果的處理,接着上面建立的函數run()es6

run().then(function(data){
		console.log(data);
	});
複製代碼

每一個Promise的實例對象,都有一個then的方法,這個方法就是用來處理以前各類異步邏輯的結果。github

那麼, 各位可能會問, 這麼作有什麼卵用?web

固然有用,到目前爲止,咱們學會了Promise的基本流程,可是這種用法和嵌套回調函數彷佛沒什麼區別,並且增長了複雜度。可是咱們說了,Promise的用處,其實是在於多重異步操做相互依賴的狀況下,對於邏輯流程的控制。Promise正是經過對兩種狀態的控制,以此來解決流程的控制。請看以下代碼:數組

run().then(function(data){
		//處理resolve的代碼
		cosnole.log("Promise被置爲resolve",data);
	},function(data){
		//處理reject的代碼
		cosnole.log("程序被置爲了reject",data);
	})
複製代碼

若是異步操做得到了咱們想要的結果,那咱們將調用resolve函數,在then的第一個做爲參數的匿名函數中能夠獲取數據,若是咱們獲得了錯誤的結果,調用reject函數,在then函數的第二個做爲參數的匿名函數中獲取錯誤處理數據。 這樣,一個次完整的Promise調用就結束了。對於Promise的then()方法,then老是會返回一個Promise實例,所以你能夠一直調用then,形如run().then().then().then().then().then()..... 在一個then()方法調用異步處理成功的狀態時,你既能夠return一個肯定的「值」,也能夠再次返回一個Promise實例,當返回的是一個確切的值的時候,then會將這個確切的值傳入一個默認的Promise實例,而且這個Promise實例會當即置爲fulfilled狀態,以供接下來的then方法裏使用。以下所示:

run().then(function(data){
		console.log("第一次",data);
		return data;
	}).then(function(data){
		console.log("第二次",data);
		return data;
	}).then(function(data){
		console.log("第三次",data);
		return data;
	});
	/* 異步處理成功的打印結果: 第一次 resolve0.49040459200760167d.js:18 第二次 resolve0.49040459200760167d.js:21 第三次 resolve0.49040459200760167 由此可知then方法能夠無限調用下去。 */
複製代碼

根據這個特性,咱們就能夠將相互依賴的多個異步邏輯,進行比較順序的管理了。下面舉一個擁有3個異步操做的例子,代碼有些長。

//第一個異步任務
	function run_a(){
		return new Promise(function(resolve, reject){
			//假設已經進行了異步操做,而且得到了數據
			resolve("step1");
		});
	}
	//第二個異步任務
	function run_b(data_a){
		return new Promise(function(resolve, reject){
			//假設已經進行了異步操做,而且得到了數據
			console.log(data_a);
			resolve("step2");
		});
	}
	//第三個異步任務
	function run_c(data_b){
		return new Promise(function(resolve, reject){
			//假設已經進行了異步操做,而且得到了數據
			console.log(data_b);
			resolve("step3");
		});
	}
	
	//連續調用
	run_a().then(function(data){
		return run_b(data);
	}).then(function(data){
		return run_c(data);
	}).then(function(data){
		console.log(data);
	});
	
	/*運行結果 step1 step2 step3 */
	
複製代碼

這樣,連續依賴的幾個異步操做,就完成了,解決了讓人頭痛的回調地獄問題。

異步操做拒絕及終止調用鏈

前文提到過,then方法能夠接收兩個匿名函數做爲參數,第一個參數是Promise置爲fulfilled狀態後的回調,第二個是置爲rejected狀態的回調。在不少狀況下,若是連續的幾個異步任務,其中某個異步任務處理失敗,那麼接下來的幾個任務很大程度上就不須要繼續處理了,那麼咱們該如何終止then的調用鏈呢?在Promsie的實例上,除了then方法外,還有一個catch方法,catch方法的具體做用,咱們沿用上面的代碼,將run_a()改造一下來看:

//修改run_a的一步操做可能存在拒絕狀態
	function run_a(){
		return new Promise(function(resolve, reject){
			setTimeout(function(){
				if(Math.random()>.5){
					resolve("step1");
				}else{
					reject("error");
				}
			},1000);
		});
	}
	
	//這樣作不會終止
	run_a().then(function(data){
		return run_b(data);
	},function(data){
		//若是是這樣處理rejected狀態,並不會終止調用鏈
		return data;
	}).then(function(data){
		return run_c(data);
	}).then(function(data){
		console.log(data);
	});
	
	//在調用鏈的末尾加上catch方法,當某個環節的Promise的異步處理出錯時,將終止其後的調用,直接跳到最後的catch
	run_a().then(function(data){
		return run_b(data);
	}).then(function(data){
		return run_c(data);
	}).then(function(data){
		console.log(data);
	}).catch(function(e){
		//rejected的狀態將直接跳到catch裏,剩下的調用不會再繼續
		console.log(e);
	});
複製代碼

以上代碼簡單描述瞭如何終止鏈式調用,值得注意的是,catch方法還有try catch的做用,也就是說,then裏面的邏輯代碼若是出現了錯誤,並不會在控制檯拋出,而是會直接有catch捕獲。

ES6對Promise/A+的擴展

Promise的提出的時間很早,其標準稱爲Promise/A+,內容很少,ES6將Promise寫入了標準,並在其基礎上進行了擴展,具體能夠參考:

· 由malcolm yud對Promise/A+的翻譯 · 阮一峯ES6入門—Promise

這裏將講一下ES6對Promise標準的擴展,也能夠直接看上面的參考連接

Promise.all的擴展


本擴展實現了將多個異步操做合併爲一個操做,也就是並行處理異步,最後統一操做結果,注意:本方法只能經過Promise對象直接調用,實例不能進行此操做。

all()接收一個參數數組,數組中的每一項都對應一個

//第一個異步任務
	function run_a(){
		return new Promise(function(resolve, reject){                
			//假設已經進行了異步操做,而且得到了數據
			resolve("step1")
		});
	}
	//第二個異步任務
	function run_b(){
		return new Promise(function(resolve, reject){
			//假設已經進行了異步操做,而且得到了數據
			resolve("step2");
		});
	}
	//第三個異步任務
	function run_c(){
		return new Promise(function(resolve, reject){
			//假設已經進行了異步操做,而且得到了數據
			resolve("step3");
		});
	}
	
	Promise.all([run_a(),run_b(),run_c()]).then(function(data){
		console.log(data);
	},function(data){
		console.log(data);
	});
	/*打印結果 ["step1","step2","step3"] */
	
	//修改第二個異步任務
	//第一個異步任務
	function run_b(){
		return new Promise(function(resolve, reject){                
			//假設已經進行了異步操做,而且得到了數據
			reject("step2")
		});
	}
	/*打印結果 *捕獲了第一個出現的拒絕狀態的數據 ["step2"] */
複製代碼

由上所示,並行運算的結果將按照入參順序放在放在數組裏返回。

Promise.race的擴展


race本意爲賽跑,顧名思義,race的用法就是並列的幾個異步操做,誰先處理結束就以誰爲準。

//第一個異步任務
	function run_a(){
		return new Promise(function(resolve, reject){
			setTimeout(function(){
                console.log("執行到step1");                
                resolve("step1")
			},3000);
		});
	}
	//第二個異步任務
	function run_b(){
		return new Promise(function(resolve, reject){
            setTimeout(function(){
                console.log("執行到step2");
                resolve("step2");
            },1000);
		});
	}
	//第三個異步任務
	function run_c(){
		return new Promise(function(resolve, reject){
            setTimeout(function(){
                console.log("執行到step3");
                resolve("step3");
            },3000);
		});
	}

    Promise.race([run_a(),run_b(),run_c()]).then(function(results){
        console.log(results);
    },function(data){
        console.log(data);
    });
    /* 打印結果: 執行到step2 step2 執行到step1 執行到step3 */
複製代碼

能夠看出,run_b先執行完,進入了then函數進行回調,但須要注意的是,第一個結束的異步操做回調後,其它的異步操做還會繼續執行,只是並不會繼續進入then了而已。

結語

以上就是對ES6標準下的Promise的簡單理解,Promise出現的時間不短,在不少開源項目裏被普遍應用,理解了Promise,有助於對代碼的進一步理解。本篇文章做爲我我的的學習記錄,但願起到拋磚引玉的做用,對你們的學習起到點點做用。

相關文章
相關標籤/搜索