原創文章&經驗總結&從校招到A廠一路陽光一路滄桑
詳情請戳www.coderccc.com
> 主要知識點:Promise生命週期、Promise基本操作、Promise鏈、響應多個Promise以及集成Promise
1. Promise基礎
什麼是回調地獄?
當使用回調函數來進行事件處理的時候,如果嵌套多層回調函數的時候,就會出現回調地獄,例如:
method1(function(err, result) {
if (err) {
throw err;
}
method2(function(err, result) {
if (err) {
throw err;
}
method3(function(err, result) {
if (err) {
throw err;
}
method4(function(err, result) {
if (err) {
throw err;
}
method5(result);
});
});
});
});
像本例一樣嵌套多個方法調用會創建錯綜複雜的代碼,會難以理解與調試。當想要實現更復
雜的功能時,回調函數也會存在問題。要是你想讓兩個異步操作並行運行,並且在它們都結
束後提醒你,那該怎麼做?要是你想同時啓動兩個異步操作,但只採用首個結束的結果,那
又該怎麼做?而使用Promise就能避免回調地獄的情況。
Promise可以當做是一個佔位符,表示異步操作的執行結果。函數可以返回一個Promise,而不必訂閱一個事件或者向函數傳遞一個回調函數。
Promise的生命週期
每個 Promise 都會經歷一個短暫的生命週期,初始爲掛起狀態(pending state) ,這表示異步操作尚未結束。一個掛起的 Promise 也被認爲是未決的(unsettled )。一旦異步操作結束, Promise就會被認爲是已決的(settled ) ,並進入兩種可能狀態之一:
- 已完成(fulfilled ) : Promise 的異步操作已成功結束;
- 已拒絕(rejected ) : Promise 的異步操作未成功結束,可能是一個錯誤,或由其他原因導致。
內部的[[PromiseState]]
屬性會被設置爲"pending"
、 "fulfilled"
或 "rejected",以反映
Promise的狀態。該屬性並未在 Promise 對象上被暴露出來,
因此你無法以編程方式判斷 Promise
到底處於哪種狀態。不過你可以使用then()
方法在 Promise 的狀態改變時執行一些特定操作。
-
then()方法
then()
方法在所有的 Promise 上都存在,並且接受兩個參數。第一個參數是 Promise 被完成時要調用的函數,異步操作的結果數據都會被傳入這個完成函數。第二個參數則是 Promise 被拒絕時要調用的函數,與完成函數相似,拒絕函數會被傳入與拒絕相關聯的任何附加數據。then()方法的兩個參數是可選的,因此可以自由組合監聽完成和失敗的處理函數; -
catch()方法
Promise有catch()方法,等同於只傳遞拒絕處理函數給then()方法:
promise.catch(function(err) { // 拒絕 console.error(err.message); }); // 等同於: promise.then(null, function(err) { // 拒絕 console.error(err.message); });
創建未決的Promise
使用Promise構造器可以創建一個Promise實例,此構造器接收一個參數:一個被稱之爲執行器(excutor)的函數,該函數包含了resolve()
函數和reject()
函數這兩個參數。resolve()
函數在異步任務執行成功時調用,而reject()
函數在異步任務執行失敗時調用。例如:
let promise = new Promise(function(resolve,reject){
console.log('hi, promise');
resolve();
});
promise.then(()=>{
console.log('hi, then');
});
console.log('hi');
輸出:
hi, promise
hi
hi then
從輸出結果可以看出,Promise構造器中的代碼是最先執行的,而then()
代碼是最後執行的,這是因爲只有在Promise中的處理器函數執行結束之後,then()方法中的完成處理函數或者拒絕處理函數纔會添加到作業隊列的尾部。
創建已決的Promise
- 使用
Promise.resolve()
Promise.resolve()
方法接收一個參數,並會返回一個處於已完成狀態的 Promise
,在then()
方法中使用完成處理函數才能提取該完成態的Promise
傳遞的值,例如:
let promise = Promise.resolve('hi');
promise.then((value)=>{
console.log(value); //hi
});
- 使用Promise.reject()
可以使用Promise.reject()
方法來創建一個已拒絕狀態的Promise
,同樣只有在拒絕處理函數中或者catch()
方法中才能接受reject()
方法傳遞的值:
let reject = Promise.reject('reject');
reject.catch((value)=>{
console.log(value); //reject
})
非Promise的thenable
當一個對象擁有一個能接受resolve
與reject
參數的then()
方法時,該對象就會被認爲是一個非Promise
的thenable
,例如:
let thenable = {
then:function(resolve,reject){
resolve('hi');
}
}
Promise.resolve()
與Promise.reject()
方法都能夠接受非Promise的thenable作爲參數,當傳入了非Promise的thenable時,這些方法會創建一個新的Promise,並且可以使用then()方法對不同狀態進行操作:
創建一個已完成的Promise
let thenable = {
then:function(resolve,reject){
resolve('hi');
}
}
let promise = Promise.resolve(thenable);
promise.then((value)=>{
console.log(value); //hi
});
同樣利用thenable可以創建一個已拒絕的Promise:
let thenable = {
then:function(resolve,reject){
reject('hi');
}
}
let promise = Promise.resolve(thenable);
promise.then(null,(value)=>{
console.log(value);
});
執行器錯誤
當執行器內部拋出錯誤,那麼Promise的拒絕處理函數就會被調用,例如:
let promise = new Promise(function(resolve,reject){
throw new Error('Error!');
})
promise.catch(function(msg){
console.log(msg); //error
})
2. Promise鏈
除了使用單個Promise外,多個Promise可以進行級聯使用,實際上then()
方法或者catch()
方法會返回一個新的Promise,僅當前一個Promise被決議之後,後一個Promise纔會進行處理。
串聯Promise
let p1 = new Promise(function(resolve,reject){
resolve('hi');
});
p1.then((value)=>{
console.log(value);
throw new Error('Error!');
}).catch(function(error){
console.log(error);
})
可以看出當p1的then()
方法執行結束後會返回一個Promise,因此,在此基礎上可以繼續執行catch()
方法。同時,Promise鏈允許捕獲前一個Promise的錯誤。
Promise鏈中傳遞值
**Promise鏈的另一個重要方面是能從一個Promise傳遞數據給另一個Promise的能力。**前一個Promise的完成處理函數的返回值,傳遞到下一個Promise中。
//Promise鏈傳遞值
let p1 = new Promise(function(resolve,reject){
resolve(1);
})
p1.then(value=>value+1)
.then(value=>{
console.log(value);
})
p1的完成處理函數返回了value+1
,也就是2
,會傳入到下一個Promise的完成處理函數,因此,第二個then()
方法中的完成處理函數就會輸出2
。拒絕處理函數同樣可以被用於在Promise鏈中傳遞數據。
Promise鏈中傳遞Promise
在完成或者拒絕處理函數中可以返回基本類型值,從而可以在Promise鏈中傳遞。另外,在Promise鏈中也可以傳遞對象,如果傳遞的是Promise對象,就需要額外的處理:
傳遞已完成狀態的Promise:
let p1 = new Promise(function(resolve,reject){
resolve(1);
});
let p2 = new Promise(function(resolve,reject){
resolve(2);
})
p1.then(value=>{
console.log(value);
return p2;
}).then(value=>{
console.log(value);
});
輸出:1 2
p1中返回了Promise對象p2
,當p2
完成時,纔會調用第二個then()
方法,將值value
傳到完成處理函數中。若Promise
對象p2
被拒絕後,第二個then()
方法中的完成處理函數就不會執行,只能通過拒絕處理函數才能接收到p2傳遞的值:
let p1 = new Promise(function(resolve,reject){
resolve(1);
});
let p2 = new Promise(function(resolve,reject){
reject(2);
})
p1.then(value=>{
console.log(value);
return p2;
}).catch(value=>{
console.log(value);
});
3. 響應多個Promise
如果想監視多個Promise的狀態,從而決定下一步動作,可以使用ES6提供的兩個方法:Promise.all()
和Promise.race()
;
Promise.all()
Promise.all()方法能接受單個可迭代對象(如數組)作爲參數,可迭代對象的元素都是Promise。該方法會返回一個Promise,只有傳入所有的Promise都已完成,所返回的Promise纔會完成,例如:
//Promise.all()
let p1 = new Promise(function(resolve,reject){
resolve(1);
})
let p2 = new Promise(function(resolve,reject){
resolve(2);
})
let p3 = new Promise(function(resolve,reject){
resolve(3);
})
let p4 = Promise.all([p1,p2,p3]);
p4.then(value=>{
console.log(Array.isArray(value)); //true
console.log(value); //[1,2,3]
})
對 Promise.all()
的調用創建了新的Promise p4
,在 p1
、 p2
與 p3
都被完成後, p4
最終會也被完成。傳遞給 p4
的完成處理函數的結果是一個包含每個決議值(1 、 2 與 3 ) 的數組,這些值的存儲順序保持了待決議的 Promise
的順序(與完成的先後順序無關) ,因此你可以將結果匹配到每個Promise
。
若傳遞給Promise.all()
的某個 Promise 被拒絕了,那麼方法所返回的 Promise 就會立刻被拒絕,而不必等待其他的 Promise 結束:
//Promise.all()
let p1 = new Promise(function(resolve,reject){
resolve(1);
})
let p2 = new Promise(function(resolve,reject){
reject(2);
})
let p3 = new Promise(function(resolve,reject){
resolve(3);
})
let p4 = Promise.all([p1,p2,p3]);
p4.catch(value=>{
console.log(Array.isArray(value)); //true
console.log(value); //2
})
在此例中, p2 被使用數值 2 進行了拒絕,則 p4 的拒絕處理函數就立刻被調用,而不會
等待 p1 或 p3 結束執行(它們仍然會各自結束執行,只是 p4 不等它們) 。
拒絕處理函數總會接受到單個值,而不是一個數組。該值是被拒絕的Promise所返回的拒絕值。
Promise.race()
Promise.race()
方法接收一個元素是Promise的可迭代對象,並返回一個新的Promise。一旦傳入Promise.race()
的可迭代對象中有一個Promise是已決狀態,那麼返回的Promise對象就會立刻成爲已決狀態。
而Promise.all()
方法得必須等到所有傳入的Promise全部變爲已決狀態,所返回的Promise纔會已決。
let p1 = new Promise(function(resolve,reject){
resolve(1);
})
let p2 = new Promise(function(resolve,reject){
resolve(2);
})
let p3 = new Promise(function(resolve,reject){
resolve(3);
})
let p4 = Promise.race([p1,p2,p3]);
p4.then(value=>{
console.log(Array.isArray(value)); //false
console.log(value); //1
})
Promise.race() 方法傳入的Promise中哪一個Promise先變成已完成狀態,就會將值傳遞給所返回的Promise對象的完成處理函數中。若哪一個Promise最先變成已拒絕狀態,同樣的,會將值傳遞給p4
的拒絕處理函數中。
4. 繼承Promise
可以繼承Promise實現自定義的Promise,例如:
class MyPromise extends Promise {
// 使用默認構造器
success(resolve, reject) {
return this.then(resolve, reject);
}
failure(reject) {
return this.catch(reject);
}
}
let promise = new MyPromise(function(resolve, reject) {
resolve(42);
});
promise.success(function(value) {
console.log(value); // 42
}).failure(function(value) {
console.log(value);
});
在此例中, MyPromise 從 Promise 上派生出來,並擁有兩個附加方法。 success()
方法模擬了 resolve()
, failure()
方法則模擬了 reject()
。
5. 總結
-
Promise 具有三種狀態:掛起、已完成、已拒絕。一個
Promise
起始於掛起態,並在成功時轉爲完成態,或在失敗時轉爲拒絕態。then()
方法允許你綁定完成處理函數與拒絕處理函數,而catch()
方法則只允許你綁定拒絕處理函數; -
能夠將多個Promise串聯起來組成Promise鏈,並且能夠在中間傳遞值,甚至是傳遞Promise對象。 then() 的調用都創建並返回了一個新的 Promise ,只有在前一個 Promise 被決議過,新 Promise 也會被決議。 同時也可以使用Promise.all()和Promise.race()方法來管理多個Promise。