Promise是面試中最多見的問題之一,也是ES6中頗有用、很核心的一個新特性。尤爲在現在異步操做愈來愈多、愈來愈複雜的狀況下,Prmoise更是顯示出了它強大而又優雅的本質。這篇文章,咱們來系統地講解一下Promise相關的核心知識點。git
本文已同步至個人我的主頁。歡迎訪問查看更多內容!若有錯誤或不足,歡迎隨時探討交流。謝謝你們的關注和支持!github
本文,咱們按照如下的思路來逐步深刻Promise:web
Promise
是一種異步編程的解決方案,它能夠將異步操做以同步的流程表達出來,它比傳統的使用回調函數和事件來處理異步問題更加合理,更符合人們線性處理問題的邏輯。 從語法上說,Promise
是一個對象,它裏面保存着一個未來纔會發生的事情(通常是一個異步操做)的狀態和結果。面試
聽起來,有些抽象,全是概念性的東西。那接下來咱們看看爲何ES6中會出現Promise
?經過具體示例,能夠幫助咱們更好的理解什麼是Promise
。編程
在ES6出現Promise以前,咱們要處理一個異步請求,一般是這樣的:數組
// 利用回調函數來處理異步請求結果
// 不少異步請求方法也會設計一些事件,在事件中處理異步請求結果
asyncRequest(function(resData) {
// 處理請求結果
});
複製代碼
這樣看着沒什麼問題,但需求老是各類各樣甚至是變態的。若是咱們須要在第一個請求返回結果後再發起第二個請求呢?再若是,第二個請求結果返回後後,咱們須要再發起第三個請求呢?以後,再是第四個...第五個......此時,代碼應該會變成這樣:promise
asyncRequest1(function(resData1) {
asyncRequest2(function(resData2) {
asyncRequest3(function(resData3) {
asyncRequest4(function(resData4) {
asyncRequest5(function(resData5) {
// ......
// 處理請求結果
});
});
});
});
});
複製代碼
這時,代碼嵌套層次太深,再加上每次請求結束咱們應該還須要作一些適當的邏輯處理,這樣每一個處理請求結果的地方還須要額外的代碼,這樣整個代碼塊顯得很臃腫,一點也不優雅!最主要的是,這樣的代碼很容易出錯,並且出錯後不容易定位錯誤,閱讀和維護起來十分費勁。bash
這就是異步編程最讓人頭疼和無語的地方:因爲異步操做嵌套層次過深而致使的「回調地獄」!異步
出現這種狀況,就須要思考新的異步編程的處理方法。有沒有什麼方法能在知足上面例子的需求的同時又能解決這種嵌套式的回調地獄呢?能不能不使用嵌套式回調,而使用鏈式回調呢?確定是有的,這也就是Promise
出現的緣由。同時,能不能最好不使用回調的方式來處理異步請求呢?固然也是能夠的,這就是咱們後面文章會講的async/awiat
。async
咱們來看看,上面的例子,若是使用Promise來實現是什麼樣子?應該是這樣:
new Promise(asyncRequest1)
.then(asyncRequest2(resData1))
.then(asyncRequest3(resData2))
.then(asyncRequest4(resData3))
.then(asyncRequest5(resData4))
.catch(handleError(errorMsg))
複製代碼
上面的例子,只有每一個一步請求asyncRequest
成功返回結果,纔會進入下一個.then()
方法中,從而進行下一個異步請求......以此類推。當任何一個請求出錯時,就會進入.catch()
方法中,能夠在這裏處理錯誤。這樣的鏈式回調,既知足前面例子的需求,同時又避免了嵌套回調,從而避免了「回調地獄」的出現。
這裏具體的語法看不懂,沒關係!不要慌!這個例子只是爲了說明Promise
是如何用鏈式回調來解決嵌套回調地獄的。接下來,咱們就來講說如何使用Promise
,講講它的基本語法。
ES6中規定,Promise
是一個構造函數,能夠用來實例化一個Promise
對象。下面是一個簡單的例子:
// Promise構造函數接收一個函數做爲參數
let promise = new Promise(Function);
複製代碼
文章最開始介紹什麼是Promise的時候說過:Promise
是一個對象,它裏面保存着一個未來纔會發生的事情(通常是一個異步操做)的狀態和結果。
咱們先來看看Promise表明的異步操做的狀態有哪幾種?——一共只有三種狀態:
這三種狀態,不會共存,Promise
只會處於其中某一種狀態。當異步請求開始而且未結束(沒有返回結果)時,處於pending
狀態。當異步請求返回結果後,能夠根據請求返回的結果將Promise
的狀態修改成fulfilled
或者rejected
。而且,一旦Promise
的狀態第一次改變,就不再能更改成其它任何狀態。因此,Promise
的狀態改變過程只有兩種狀況:
那麼,如何修改Promise
的狀態呢?這就須要瞭解調用Promise
構造函數時,傳遞給構造函數的Function
參數了。Promise
會爲這個函數設置兩個參數,resolve
、reject
。這兩個參數是兩個函數,由JavaScript引擎提供,不用本身部署。
resolve()
函數,能夠將Promise
的狀態由pending
改變爲fulfilled
。reject()
函數,能夠將Promise
的狀態由pending
改變爲rejected
。
這裏有兩點須要注意的地方!!
Promise
內部只有用resolve()
、reject()
才能改變它的狀態。return
任何值(包括一個Error實例)都不會改變它的狀態。throw
任何值,還會引發報錯!resolve()
、reject()
和return
的意義不一樣。他們只是改變了Promise
的狀態,並不會結束代碼執行。也就是說resolve()
、reject()
以後的代碼依舊會執行。(雖然不建議在他們後面再有代碼出現)Promise
時,參數函數中異步操做以外的同步代碼都會當即執行。來看一個示例,簡單明瞭地理解上面的文字。
let promise = new Promise(function(resolve, reject) {
// 下面兩行代碼會當即執行,不會等待異步操做結果返回、狀態改變
let a = '123';
console.log(a); // '123'
// 一個異步請求
asyncRequest(function(resData) {
if (/* 異步操做成功 */){
// 將Promise的狀態改成fulfilled(已成功)
resolve(resData); // resData通常是異步操做的結果
} else {
// 將Promise的狀態改成rejected(已失敗)
reject(resData); // resData通常是一些錯誤信息
}
});
});
複製代碼
上面的例子,在Promise
內部發起了一個異步請求,當請求完成,拿到返回值resData
時,咱們能夠根據具體的業務需求修改Promise
的對應狀態。
細心的同窗會發現,上面的例子中,咱們在resolve()
和reject()
函數中傳入了參數resData
,這是在幹什麼?還記得麼?Promise
不只能保存異步操做的狀態,還能保存異步操做的結果。咱們將將異步操做的結果resData
傳給這兩個函數,就是將其保存到了Promise
對象中。
那麼,Prmoise
對象中保存了異步操做的最終狀態和結果,咱們如何獲取呢?換句話說,咱們怎麼知道異步操做的狀態和結果分別是什麼呢?
其實,每一個Promise
的對象實例都會有一個.then()
和.catch()
方法,這兩個方法都接收一個函數做爲參數,這個函數會被Promise
傳入一個參數,這個參數就是傳入resolve()
、reject()
方法中的異步請求的結果(上個例子中的resData
)。當Promise
內部狀態變爲fulfilled
時,就會進入.then()
方法中,執行裏面的回調函數。同理,當Promise
內部狀態變爲rejected
時,就會進入.catch()
方法中,執行裏面的回調函數。
/***接着上面例子***/
promise.then(function(resData) {
// promise狀態變爲fulfilled,執行這裏
console.log(resData);
}).catch(function(resData) {
// promise狀態變爲rejected,執行這裏
console.log(resData);
});
複製代碼
像上面這樣,當執行進入.then()
中,就說明Promise
的狀態是fulfilled
。進入.catch()
中,就說明狀態是rejected
,通常會在這裏進行錯誤處理。同時,異步操做的結果會被傳入定義在.then()
、.catch()
內部的函數中,咱們能夠直接訪問使用。
**在.then()/.catch()
的返回值依舊是一個Promise
實例。**也就是說,在.then()/.catch()
中return
任何值,都會被轉化成一個Promise
實例。因此.then()
後面能夠鏈式繼續調用.then()/.catch
,.catch()
後面一樣也能夠。因而,就有可能出現下面這樣的代碼:
// 這樣的代碼是徹底沒有問題的。
promise.then(function(resData) {
// 一些代碼
}).then(function(resData) {
// 一些代碼
}).catch(function(error) {
// 一些代碼
}).then(function(resData) {
// 一些代碼
}).catch(function(error) {
// 一些代碼
});
複製代碼
這裏有一些須要注意的地方!!
.then()
中return
任何值(包括一個Error
實例),都會進入後面最鄰近的.then()
。.then()
中throw
任何值或者內部代碼報錯,都會進入後面最鄰近的.catch()
。.catch()
中狀況與.then()
徹底一致。Promise.resolve
方法接收一個任意值做爲參數,能夠將其轉換爲Promise對象。
該方法對參數的處理,能夠分爲如下四種不一樣的狀況:
此時,Promise.resolve
方法將不會作任何轉換,直接原封不動的返回這個實例。
thenable
對象是指對象內部實現了then
方法的對象。此時,Promise.resolve
方法會先將該對象轉換爲Promise
對象,而後當即執行參數對象本身的then
方法。
最終,轉換成的Promise
對象的狀態徹底依賴於它內部then
方法的具體實現,不必定是fulfilled
狀態,也有多是rejected
。
// 定義一個thenable對象
let thenable = {
then: function(resolve, reject) {
resolve(42);
// 若是換成執行下面一行代碼,後面將會進入.catch()中
// reject('error');
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
}).catch(function(value) {
console.log(value); // 'error'
});
複製代碼
若是參數不是thenable
對象,或者不是一個對象,Promise.resolve
方法返回一個新的Promise
對象,狀態爲fulfilled
,對象保存的值就是這個參數值。
Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'));
let p = Promise.resolve('Hello');
p.then(function (s){
console.log(s); // 'Hello'
});
複製代碼
Promise.resolve
方法容許調用時不帶參數,直接返回一個fulfilled
狀態的Promise
對象,對象保存的值爲undefined
。
let p = Promise.resolve();
p.then(function (value) {
console.log(value); // undefined
});
複製代碼
Promise.reject
方法也會返回一個新的Promise
實例。不論傳入的參數是什麼數據類型,有沒有thenable
方法,該實例的狀態必定爲爲rejected
,且返回的Promise
對象中保存的值就是傳入Promise.reject
方法時原封不動的參數值。
例子一
let p = Promise.reject('error');
// 等同於
let p = new Promise((resolve, reject) => reject('error'));
// 例子二
let thenable = {
then(resolve, reject) {
/**
* 不論執行下面的哪一行,
* 最後Promise對象的狀態都是rejected,
* 都會進入.catch中
*
reject('error');
// resolve('fulfilled');
}
};
Promise.reject(thenable).then(data => {
// 不會進入這裏!!
console.log('進入then!');
}).catch(e => {
console.log('進入catch!');
// !注意!這裏的e的值是傳入Promise.reject()方法的thenable對象
console.log(e === thenable); // true
})
複製代碼
Promise.all
方法用於將多個Promise
實例,包裝成一個新的Promise
實例。
let p = Promise.all([p1, p2, p3]);
複製代碼
Promise.all
方法接收一個數組做爲參數,數組元素p1
/p2
/p3
都是Promise
實例。若是不是,就會先調用Promise.resolve()
方法,將參數轉爲Promise
實例,再進一步處理。
最終,p
的狀態由p1
/p2
/p3
共同決定,分紅兩種狀況:
p1
/p2
/p3
的狀態都變成fulfilled
,p
的狀態纔會變成fulfilled
。此時p1
/p2
/p3
的返回值組成一個數組,傳遞給p
的回調函數。p1
/p2
/p3
之中有一個被rejected
,p
的狀態就變成rejected
。此時第一個被reject
的實例的返回值,會傳遞給p
的回調函數。Promise.race
方法一樣是將多個Promise
實例,包裝成一個新的Promise
實例。
let p = Promise.race([p1, p2, p3]);
複製代碼
Promise.race
方法接收的參數與Promise.all
方法同樣,若是不是 Promise
實例,就會先調用Promise.resolve()
方法,將參數轉爲 Promise
實例,再進一步處理。
上面的例子中,只要p1
/p2
/p3
之中任意一個實例率先改變狀態,不論變爲哪一種狀態,p
的狀態就跟着改變。那個率先改變狀態的Promise
實例的返回值,就傳遞給p
的回調函數。
《面試精選之Promise》——這篇文章也寫得很好,本文參考借鑑了一些地方,推薦你們能夠結合着一塊兒看。