ES6中的Promise詳解
Promise 在 JavaScript 中很早就有各種的開源實現,ES6 將其納入了官方標準,提供了原生 api 支援,使用更加便捷。
定義
Promise 是一個物件,它用來標識 JavaScript 中非同步操作的狀態(pending, resolve, reject)及結果(data)。
從控制檯打印出來一個Promise 物件來看下
可以看到,它是一個建構函式,既有屬於自己私有的 resolve, reject, all, race等方法,也有protype 原型上的 then, catch等方法。
基本用法
- 模擬問題:3秒後請求完成,返回資料res.data
var p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('res.data');
}, 3000);
});
p.then((val) => {
console.log(val);
// 'res.data'
});
console.log(p);
// [object Promise]
Promise.resolve() 的用法
Promise.resolve()方法可以將現有物件轉為Promise 物件。
var p = Promise.resolve($.ajax('/something.data'));
p.then((val ) => {console.log(val)});
它等價於
var p = new Promise(resolve => {
resolve($.ajax('/something.data'))
});
p.then((val) => {console.log(val)});
Promise.reject() 用法
此方法和Promise.resolve()方法類似,除了rejecet 代表狀態為 Rejected,不多說。
Promise.all() 用法
用於將多個Promise 例項包裝成一個新的 Promise例項,引數為一組 Promise 例項組成的陣列。
var p = Promise .all([p1,p2,p3]);
當 p1, p2, p3 狀態都 Resolved 的時候,p 的狀態才會 Resolved;只要有一個例項 Rejected ,此時第一個被 Rejected 的例項的返回值就會傳遞給 P 的回撥函式。
應用場景:假設有一個介面,需要其它兩個介面的資料作為引數,此時就要等那兩個介面完成後才能執行請求。
var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'P1');
});
var p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'P2');
});
// 同時執行p1和p2,並在它們都完成後執行then:
Promise.all([p1, p2]).then((results) => {
console.log(results); // 獲得一個Array: ['P1', 'P2']
});
Promise.race() 用法
和Promise.all 類似,區別是 Promise.race() 只要監聽到其中某一個例項改變狀態,它的狀態就跟著改變,並將那個改變狀態例項的返回值傳遞給回撥函式。
應用場景: 可以通過多個非同步任務來進行容錯處理,多個介面返回同樣的資料,只要有一個介面生效返回資料即可。
Promise.prototype.then()
then 方法是定義在 Promise 的原型物件上的,作用是為 Promise 例項新增狀態改變時的回撥函式;
then() 返回一個新的Promise 例項,因此可以支援鏈式寫法。
鏈式寫法的一個例子//來自廖雪峰
// 0.5秒後返回input*input的計算結果:
function multiply(input) {
return new Promise((resolve, reject) => {
console.log('calculating ' + input + ' x ' + input + '...');
setTimeout(resolve, 500, input * input);
});
}
// 0.5秒後返回input+input的計算結果:
function add(input) {
return new Promise((resolve, reject) => {
console.log('calculating ' + input + ' + ' + input + '...');
setTimeout(resolve, 500, input + input);
});
}
var p = new Promise((resolve, reject) => {
console.log('start new Promise...');
resolve(123);
});
p.then(multiply)
.then(add)
.then(multiply)
.then(add)
.then(function (result) {
console.log('Got value: ' + result);
});
Promise.prototype.catch()
catch 方法是一個語法糖,看下面程式碼就明白了,用於指定發生錯誤時的回撥函式。
var p = new Promise((resolve, rejecet) => {
if (...) {resolve()};
else {reject()};
})
p.then((val) => {
console.log('resolve:', val);
}).catch((err) => console.log('reject:', err));
// 等同於
p.then((data) => {
console.log(data);
}, (err) => {
console.log(err);
})
// 後一種寫法更好,語義更清晰,第一種方法在第一個函式裡面出錯的話時在第二個函式裡監聽不到變化的。
Promise.try()
實際開發中,經常遇到一種情況:不知道或者不想區分,函式 f 是同步函式還是非同步操作,但是想用 Promise 來處理它。因為這樣就可以不管f是否包含非同步操作,都用 then 方法指定下一步流程,用 catch 方法處理 f 丟擲的錯誤。
- 第一種方法,缺陷是不能識別同步請求。
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
- new Promise() 寫法
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
- Promise.try 寫法,替代new Promise() 方法,更簡潔。
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
總結
兩個特點
- 狀態不受外界影響,只有非同步操作的結果會影響到它,pending(進行中),reject(已失敗),resolved(已完成)
- 狀態只能改變一次
感受:
- promise 首先是一個建構函式,所以需要new 出來一個例項來使用
- 像是一個 ajax 函式外面加了一層包裹層,封裝了一下下,實現了程式碼層面的同步效果
- 很有意思~(廢話)
- 缺點也很明顯,就是程式碼語義化不夠,一眼看去都是Promise 的 API,then catch 等等,不能很快明白程式碼究竟想表達什麼意思,這也是 async 函數出現的原因,async ,可能是非同步操作的終極方案了。