什麼是Promise物件?
Promise是非同步程式設計的一種解決方案,比起傳統的解決方案(回撥函式和事件),它顯得更加的強大和方便(具體請看下文)。從語法上來講,Promise是一個物件,從它可以獲取非同步操作的訊息。Promise物件提供統一的API,各種非同步操作都可以用同樣的方法進行處理。
Promise有什麼用?
大家一致會回答,解決回撥地獄。那什麼又是回撥地獄?它有什麼缺點?
如果要執行四個步驟,依次執行,那麼傳統我們可能會這麼做:
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
複製程式碼
為了解決這種看起來不很舒服,寫起來更不舒服的寫法,我們可能會想起鏈式寫法:
var obj = {
stepOne: function () {
console.log('one');
return this;
},
stepTwo: function () {
console.log('two');
return this;
},
stepThree: function () {
console.log('three');
return this;
}
}
obj.stepOne().stepThree().stepTwo();
複製程式碼
這種寫法的核心在於,每次都返回this。 這樣的好處在於:
- 每一個操作都是獨立的一個函式
- 可以組裝,也就是我們可以控制執行的流程
那如果我們還需要一些其他需求的時候呢?比如:
- 如果上一步的結果作為下一步的輸入就更好了
- 如果出錯我能夠捕捉就更好了
- 如果我在函式中能夠控制流程就好了 ....
這個時候,就需要用到我們強大的Promise了
基礎用法
先來看看怎麼使用吧!直接上程式碼:
const p1 = new Promise(function(resolve, reject) {
// 在這裡做一些邏輯處理
// 如果非同步操作成功的時候,呼叫resolve
if (true) {
resolve('success');
} else {
// 操作失敗後我們就呼叫reject()
reject('fail');
}
})
p1.then((val) => {
console.log(val); // success
}, (err) => {
console.log(err)
})
複製程式碼
我們可以看到Promise是一個建構函式,用來生成Promise例項,它接收一個函式作為引數,這個函式有兩個引數——resolve和reject(它們也是兩個函式,已經由JavaScript引擎提供,不用自己部署)。
我們經常會在這個函式裡處理一些邏輯,如果處理成功後我們會執行resolve(將狀態從“未完成”轉為“完成”),失敗後執行reject(將狀態從“未完成”轉換為“失敗”)。
三個狀態
- pedding(未完成)
- resolved(完成)
- rejected(失敗)
例項生成之後,我們可以使用then方法指定resolved和rejected狀態的回撥函式。像上面的程式碼,就相當於執行第一個回撥函式,第二個不會執行(這個時候其實不提供第二個引數也是可以的)。
如果是執行失敗,狀態變成reject的時候,就會執行第二個回撥函式,就會輸出'fail'。但是其實我們不是很推薦這樣的寫法,我們可以將第二個回撥函式寫catch方法的形式。
const p1 = new Promise(function(resolve, reject) {
// 在這裡做一些邏輯處理
// 如果非同步操作成功的時候,呼叫resolve
if (false) {
resolve('success');
} else {
// 操作失敗後我們就呼叫reject()
reject('fail');
}
})
p1.then((val) => {
console.log(val);
}).catch((err) => {
console.log(err); // fail
})
複製程式碼
這種寫法類似於try...catch...,更加易於我們理解。這個時候我們其實就已經解決了出錯我們能夠捕捉的問題了。
控制程式碼的執行順序
採用鏈式的then,可以指定一組按照次序呼叫的回撥函式。這個時候前面的一個返回的可能是另外一個Promise物件(也就是說有非同步操作)。這樣後面的這個Promise就依賴於前面Promise物件的狀態。
const p1 = new Promise(function(resolve, reject) {
console.log(1);
setTimeout(function() {
console.log(2);
resolve();
}, 1000);
})
const p2 = new Promise(function(resolve, reject) {
console.log(3);
resolve(p1);
})
p2.then(() => {
console.log(4);
}).then(() => {
console.log(5);
})
複製程式碼
以上程式碼,我們應該注意一點p2的resolve方法將p1作為引數,也就是說p2的執行依賴於p1的執行。當p2準備好的時候,p1可能還沒準備好,這個時候p2就得等p1。
另外,需要注意的一點就是,then方法返回的是一個新的Promise例項(注意,不是之前的Promise例項),因此可以採用鏈式寫法,即then方法之後再呼叫另一個then方法。這樣,我們就可以實現了上面的需求了!
Promise的幾個重要方法
下面簡單介紹一下Promise物件的幾個方法,我們經常也會用到。
Promise.all()
直接上程式碼:
const p = Promise.all([p1, p2, p3]);
複製程式碼
Promise.all方法接收一個陣列作為引數,p1、p2、p3都是Promise例項。這個時候p的狀態由p1、p2、p3。它們的依賴機制類似於電路中的串聯,現在我們使用三條線(就是p1、p2、p3)進行串聯,p就是最後燈泡,燈泡要亮的話,三個都要成功,只要其中的一個沒有成功,那麼p的狀態就是rejected。
Promise.race()
const p = Promise.race([p1, p2, p3]);
複製程式碼
這個方法也是接受一個陣列作為引數。race的英文意思就有競賽的意思。上面的程式碼中表示,p1、p2、p3中哪個勝出(最新狀態發生改變,不管是失敗還是成功),那麼p的狀態就會跟著它改變。
Promise.resolve()
作用:將現有物件轉換成Promise物件,這樣我們就可以呼叫then方法等
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
複製程式碼
Promise.reject()
Promise.reject(reason)方法也會返回一個Promise例項,該例項狀態為rejected。
const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {
console.log(s)
});
// 出錯了
複製程式碼
參考: https://cnodejs.org/topic/560dbc826a1ed28204a1e7de
http://es6.ruanyifeng.com/#docs/promise