@探祕Promisejavascript
create by db on 2019-8-18 18:54:39
Recently revised in 2019-9-2 21:51:02前端
Hello 小夥伴們,若是以爲本文還不錯,麻煩點個贊或者給個 star,大家的贊和 star 是我前進的動力!GitHub 地址java
查閱網上諸多資料,並結合本身的學習經驗,寫下這篇學習筆記,以記錄本身的學習心得。現分享給你們,以供參考。node
I hear and I fogorget.jquery
I see and I remember.git
I do and I understand.github
咱們都知道,js的世界是單線程執行的,也就是說一個任務完成以後才能進行另外一個任務,這是由於js是運行在宿主進程多腳本語言,好比瀏覽器,好比node,宿主進程只會爲其分配一個js引擎線程。ajax
那麼對於耗時比較長的操做,例如一些ajax異步請求,這些請求之間有一種關係,就是下一次請求的參數是上一次請求的結果——回調地獄。所幸ES6給咱們提供了異步編程的一種解決方案——Promise。編程
參考文獻json
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最先提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。
所謂promise,簡單說是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果,從語法上說,promise是一個對象,從它能夠獲取異步操做的消息,promise提供了統一的API,各類異步操做均可以用一樣的方法進行處理。
Promise的優勢
對象的狀態不受外界影響。promise對象表明一個異步操做,有三種狀態,pending
(進行中)、fulfilled
(已成功)、rejected
(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態,這也是promise這個名字的由來——「承諾」;
一旦狀態改變就不會再變,任什麼時候候均可以獲得這個結果。promise對象的狀態改變,只有兩種可能:從pending
變爲fulfilled
,從pending
變爲rejected
。這時就稱爲resolved
(已定型)。若是改變已經發生了,你再對promise對象添加回調函數,也會當即獲得這個結果。這與事件(event)徹底不一樣,事件的特色是:若是你錯過了它,再去監聽是得不到結果的。
有了Promise對象,就能夠將異步操做以同步操做的流程表達出來,可使用一種鏈式調用的方式來組織代碼,避免了層層嵌套的回調函數。讓代碼更加的直觀。此外,Promise對象提供統一的接口,使得控制異步操做更加容易。
Promise的缺點
pending
狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。好比咱們在工做中常常會碰到這麼一個需求,好比我使用ajax發一個A請求後,成功後拿到數據,咱們須要把數據傳給B請求;那麼咱們須要以下編寫代碼:
$.ajax({
url: '',
dataType:'json',
success: function(data) { // 獲取data數據 傳給下一個請求
var id = data.id;
$.ajax({
url:'',
data:{"id":id},
success:function(){ // .....
}
});
}
});
複製代碼
如上代碼;上面的代碼有以下幾點缺點:
要想建立promise對象,可使用new
來調用promise的構造器來進行實例化。
以下代碼:
var promise = new Promise(function(resolve,reject){
// ... some code
if(/*異步操做成功*/) {
resolve(value) // 成功調用resolve 往下傳遞參數 且只接受一個參數
}else {
reject(error) // 失敗調用reject 往下傳遞參數 且只接受一個參數
}
});
複製代碼
對經過new
生成的promise
對象爲了設置其值在resolve
(成功) / reject
(失敗) 時調用的回調函數,可使用promise.then()
實例方法。
以下代碼:
promise.then(onFulfilled, onRejected);
複製代碼
resolve
(成功) 時 調用onFulfilled
方法,reject
(失敗) 時 調用onRejected
方法;
Promise.then
成功和失敗時均可以使用,若是出現異常的狀況下能夠採用promise.then(undefined,onRejected)
這種方式,只指定onRejected回調函數便可,不過針對這種狀況下咱們有更好的選擇是使用catch這個方法;代碼以下:
promise.catch(onRejected);
複製代碼
上面囉嗦了這麼多,咱們來分別來學習相關的promise對象中的方法知識點吧!
通常狀況下咱們都會使用new Promise()
來建立promise對象,可是咱們也可使用promise.resolve
和 promise.reject
這兩個方法;
Promise.resolve(value)
的返回值也是一個promise對象,咱們能夠對返回值進行.then調用;
以下代碼:
Promise.resolve(11).then(function(value){
console.log(value); // 打印出11
});
複製代碼
resolve(11)
代碼中,會讓promise對象進入肯定(resolve
狀態),並將參數11
傳遞給後面的then
所指定的onFulfilled
函數;
咱們上面說過建立promise對象,可使用new Promise
的形式建立對象,可是咱們這邊也可使用Promise.resolve(value)
的形式建立promise對象;
Promise.reject
也是new Promise
的快捷形式,也建立一個promise對象。
好比以下代碼:
Promise.reject(new Error(「我錯了,請原諒俺!!」));
複製代碼
就是下面的代碼new Promise的簡單形式:
new Promise(function(resolve,reject){
reject(new Error("我錯了,請原諒俺!!"));
});
複製代碼
下面咱們來綜合看看使用resolve方法和reject方法。
demo以下:
function testPromise(ready) {
return new Promise(function(resolve,reject){
if(ready) {
resolve("hello world");
}else {
reject("No thanks");
}
});
};
// 方法調用
testPromise(true).then(function(msg){
console.log(msg);
},function(error){
console.log(error);
});
複製代碼
上面的代碼的含義是給testPromise
方法傳遞一個參數,返回一個promise對象,若是爲true
的話,那麼調用promise對象中的resolve()
方法,而且把其中的參數傳遞給後面的then
第一個函數內,所以打印出 「hello world
」, 若是爲false
的話,會調用promise對象中的reject()
方法,則會進入then
的第二個函數內,會打印No thanks
;
var promise = new Promise(function(resolve){
console.log(1);
resolve(3);
});
promise.then(function(value){
console.log(value);
});
console.log(2);
複製代碼
上面的代碼輸出咱們能夠看到,結果分別爲1,2,3
;
首先代碼從上往下執行,首先輸出1
,而後調用resolve(3)
這個方法,這時候promise對象變爲肯定狀態,即調用onFulFilled
這個方法,從上面瞭解到,resolve(成功)
時 調用onFulfilled
方法,Promise.then
成功和失敗時均可以使用,所以第一個函數是成功調用的,可是Promise對象是以異步方式調用的,因此先執行console.log(2)
,輸出的是2
,而後輸出的是3
;
function ready(fn){
var readyState = document.readyState;
if (readyState === 'interactive' || readyState === 'complete') {
fn();
} else {
window.addEventListener('DOMContentLoaded', fn);
}
}
ready(function(){
console.log("DOM Load Success");
});
console.log("我是同步輸出的");
複製代碼
如上代碼;若是在調用ready()
方法以前DOM已經載入完成的話,就會對回調函數進行同步調用,先輸出DOM Load Success
後輸出 我是同步輸出的
;若是在調用ready()
方法以前DOM爲未載入完成的話,那麼代碼先會執行 window.addEventListener(‘DOMContentLoaded’, fn);
就會異步調用該函數,那麼就會先輸出 「我是同步輸出的
」,後輸出「DOM Load Success
」; 爲了解決上面的同步或者異步混亂的問題,咱們如今可使用promise對象使用異步的方式來解決;
以下代碼:
function readyPromise(){
return new Promise(function(resolve,reject){
var readyState = document.readyState;
if (readyState === 'interactive' || readyState === 'complete') {
resolve();
} else {
window.addEventListener('DOMContentLoaded', resolve);
}
});
}
readyPromise().then(function(){
console.log("DOM Load Success");
});
console.log("我是同步加載的,先執行我");
複製代碼
輸出以下:先輸出「我是同步加載的,先執行我
」 後輸出 「DOM Load Success
」。由於promise對象是異步加載的。
Promise 對象有三種狀態:
Resolve
能夠理解爲成功的狀態;Rejected
能夠理解爲失敗的狀態;Pending
既不是Resolve
也不是Rejected
狀態;能夠理解爲Promise對象實例建立時候的初始狀態; 好比Promise對象中的resolve
方法就是調用then
對象的第一個函數,也就是成功的狀態;而reject
方法就是調用then對象的第二個函數,也就是失敗的狀態;
仍是以前的例子:
function testPromise(ready) {
return new Promise(function(resolve,reject){
if(ready) {
resolve("hello world");
}else {
reject("No thanks");
}
});
};
// 方法調用
testPromise(true).then(function(msg){
console.log(msg);
},function(error){
console.log(error);
});
複製代碼
上面的代碼就是利用了 then(onFulfilled,onRejected)
方法來執行的,第一個方法就是成功狀態的標誌,第二個方法是失敗的狀態標誌;
固然在多個任務的狀況下then方法一樣可使用;好比上面的代碼改爲以下:
function testPromise(ready) {
return new Promise(function(resolve,reject){
if(ready) {
resolve("hello world");
}else {
reject("No thanks");
}
});
};
// 方法調用
testPromise(true).then(function(msg){
console.log(msg);
}).then(testPromise2)
.then(testPromise3);
function testPromise2(){
console.log(2);
}
function testPromise3(){
console.log(3);
}
複製代碼
輸出以下:hello world ,2,3
上面的代碼是then
的鏈式調用方式,輸出是按順序輸出的 分別爲 hello world , 2,3;
使用鏈式調用的緣由是每次調用後都會返回promise對象;
Promise.catch()
方法是promise.then(undefined,onRejected)
方法的一個別名,該方法用來註冊當promise對象狀態變爲Rejected
的回調函數。
以下代碼:
var promise = Promise.reject(new Error("message"));
promise.catch(function(error) {
console.log(error);
});
複製代碼
無論是then仍是catch方法調用,都返回一個新的promise對象;
下面咱們來看看這個例子:
var promise1 = new Promise(function(resolve){
resolve(1);
});
var thenPromise = promise1.then(function(value){
console.log(value);
});
var catchPromise = thenPromise.catch(function(error){
console.log(error);
});
console.log(promise1 !== thenPromise); // true
console.log(thenPromise !== catchPromise); //true
複製代碼
如上代碼,打印的都是true
,這說明無論是then
仍是catch
都返回了和新建立的promise是不一樣的對象;
若是咱們知道了then方法每次都會建立返回一個新的promise對象的話,那麼久不難理解下面的代碼了;
以下:
var promise1 = new Promise(function(resolve){
resolve(1);
});
promise1.then(function(value){
return value * 2;
});
promise1.then(function(value){
return value * 2;
});
promise1.then(function(value){
console.log("1"+value);
});
複製代碼
如上的代碼;打印出11
;由於他們每次調用then
方法時,是使用的不一樣的promise對象;所以最後打印的value仍是1
;可是若是咱們then方法是連續調用的話,那狀況就不同了。
好比以下代碼:
var promise1 = new Promise(function(resolve){
resolve(2);
});
promise1.then(function(value){
return value * 2;
}).then(function(value){
return value * 2;
}).then(function(value){
console.log("1"+value);
});
複製代碼
打印出18
,即 「1」 + 2 * 2 * 2 = 18;
上面第一種方法沒有使用方法鏈的調用,上面第一種那種寫法then
調用幾乎是同時開始進行的,且傳給每一個then
的value都是1
;
第二種方式是使用方法鏈的then
,使多個then
方法鏈接在一塊兒了,所以函數會嚴格執行 resolve – then — then – then
的順序執行,而且傳遞每一個then
方法的value的值都是前一個promise對象中return的值;所以最後的結果就是18
了;
如今咱們再回過頭一剛開始咱們討論的爲何要使用promise的緣由的問題了,好比2個ajax請求,後一個ajax請求須要獲取到前一個ajax請求的數據,咱們以前在使用jquery寫代碼是以下的:
$.ajax({
url: '',
dataType:'json',
success: function(data) {
// 獲取data數據 傳給下一個請求
var id = data.id;
$.ajax({
url:'',
data:{"id":id},
success:function(){
// .....
}
});
}
});
複製代碼
如今咱們學習了then
方法後,咱們能夠從新編寫上面的代碼變成以下:
var ajaxPromise = new Promise(function(resolve){
resolve();
});
ajaxPromise.then(function(){
$.ajax({
url:'',
dataType:'json',
success: function(data) {
var id = data.id;
return id;
}
})
}).then(function(id){
$.ajax({
url:'',
dataType:'json',
data:{"id":id},
success: function(data){
console.log(data);
}
})
});
複製代碼
Promise.all
方法用於將多個Promise實例包裝成一個新的Promise實例。
Promise.all能夠接受一個元素爲Promise對象的數組做爲參數,當這個數組裏面全部的promise對象都變爲resolve時,該方法纔會返回。
以下代碼:
var promise1 = new Promise(function(resolve){
setTimeout(function(){
resolve(1);
},3000);
});
var promise2 = new Promise(function(resolve){
setTimeout(function(){
resolve(2);
},1000);
});
Promise.all([promise1,promise2]).then(function(value){
console.log(value); // 打印[1,2]
});
複製代碼
如上代碼 打印的是[1,2]
; 如上咱們看到promise1
對象中的setTimeout是3秒的時間,而promise2
對象中的setTimeout是1秒的時間,可是在Promise.all
方法中會按照數組的原先順序將結果返回;
在咱們平時的需求中,或許有這種狀況的需求,好比咱們須要發2個ajax請求時,無論他們的前後順序,當這2個ajax請求都同時成功後,咱們須要執行某些操做的狀況下,這種狀況很是適合。
如上可知:Promise.all
在接收到的全部對象promise都變爲FulFilled
或者 Rejected
狀態以後纔會繼續後面的處理,可是Promise.race
的含義是隻要有一個promise對象進入FulFilled或者Rejected狀態的話,程序就會中止,且會繼續後面的處理邏輯;
以下代碼:
// `delay`毫秒後執行resolve
function timerPromise(delay){
return new Promise(function(resolve){
setTimeout(function(){
resolve(delay);
},delay);
});
}
// 任何一個promise變爲resolve或reject 的話程序就中止運行
Promise.race([
timerPromise(1),
timerPromise(32),
timerPromise(64),
timerPromise(128)
]).then(function (value) {
console.log(value); // => 1
});
複製代碼
如上代碼建立了4個promise對象,這些promise對象分別在1ms,32ms,64ms,128ms後變爲肯定狀態,而且在第一個變爲肯定狀態後1ms後,then
函數就會被調用,這時候resolve()
方法給傳遞的值爲1
,所以執行then
的回調函數後,值變爲1
;
咱們再來看看當一個promise對象變爲肯定狀態(FulFiled)的時候,他們後面的promise對象是否還在運行呢?咱們繼續看以下代碼運行:
var runPromise = new Promise(function(resolve){
setTimeout(function(){
console.log(1);
resolve(2);
},500);
});
var runPromise2 = new Promise(function(resolve){
setTimeout(function(){
console.log(3);
resolve(4);
},1000);
});
// 第一個promise變爲resolve後程序中止
Promise.race([runPromise,runPromise2]).then(function(value){
console.log(value);
});
複製代碼
如上代碼是使用定時器調用的,上面是2個promise對象,咱們看到第一個promise對象過500毫秒後加入到執行隊列裏面去,若是執行隊列沒有其餘線程在運行的時候,就執行該定時器,因此第一次打印1
,而後調用resolve(2)
; 接着調用promise.race
方法,該方法只要有一個變爲成功狀態(FulFiled)的時候,程序就會中止,所以打印出2
,同時後面的promise對象接着執行,所以打印出3
,可是因爲promise.race()
該方法已經中止調用了,因此resolve(4)
不會有任何輸出;所以最後輸出的是1,2,3
;
由此咱們得出結論:當一個promise對象變爲(FulFilled)成功狀態的時候,後面的promise對象並無中止運行。
做爲javascript的新標準,ES6爲咱們提供了不少新語法及新特性,讓咱們的代碼更加簡單與易用。
做爲一隻前端菜鳥,本篇文章旨在記錄本身的學習心得,若有不足,還請多多指教,謝謝你們。
路漫漫其修遠兮,與諸君共勉。
後記:Hello 小夥伴們,若是以爲本文還不錯,記得點個贊或者給個 star,大家的贊和 star 是我編寫更多更豐富文章的動力!GitHub 地址
db 的文檔庫 由 http://www.javashuo.com/tag/db 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議進行許可。
基於github.com/danygitgit上的做品創做。
本許可協議受權以外的使用權限能夠從 creativecommons.org/licenses/by… 處得到。