衆所周知的,Javascript是一種單線程的語言,全部的代碼必須按照所謂的「自上而下」的順序來執行。本特性帶來的問題就是,一些未來的、未知的操做,必須異步實現(關於異步,我會在另外一篇文章裏進行討論)。本文將討論一個比較常見的異步解決方案——Promise,時至本文最後更新的日子,Promise的應用已經極其普遍。javascript
我相信每一個前端都遇到過這樣一個問題,當一個異步任務的執行須要依賴另外一個異步任務的結果時,咱們通常會將兩個異步任務嵌套起來,這種狀況發生一兩次還能夠忍,可是發生不少次以後,你的代碼就會變成這個熊樣:前端
async1(function(){
async2(function(){
async3(function( async4(funciton(){
async5(function(){
//(╯°□°)╯︵┻━┻
//...
});
});
));
});
});
複製代碼
這就是所謂的回調地獄,代碼層層嵌套,環環相扣,很明顯,邏輯稍微複雜一些,這樣的程序就會變得難以維護。java
對於這種狀況,程序員們想了不少解決方案(好比將代碼模塊化),但流程控制上,仍是沒有掏出})的大量嵌套。但去年ES2015的標準裏,Promise的標準化,必定程度上解決了JavaScript的流程操做問題。webpack
時至今日,不少現代瀏覽器都已經實現,可是爲了兼容,建議自行對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捕獲。
Promise的提出的時間很早,其標準稱爲Promise/A+,內容很少,ES6將Promise寫入了標準,並在其基礎上進行了擴展,具體能夠參考:
· 由malcolm yud對Promise/A+的翻譯 · 阮一峯ES6入門—Promise
這裏將講一下ES6對Promise標準的擴展,也能夠直接看上面的參考連接
本擴展實現了將多個異步操做合併爲一個操做,也就是並行處理異步,最後統一操做結果,注意:本方法只能經過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"] */
複製代碼
由上所示,並行運算的結果將按照入參順序放在放在數組裏返回。
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,有助於對代碼的進一步理解。本篇文章做爲我我的的學習記錄,但願起到拋磚引玉的做用,對你們的學習起到點點做用。