不得不說, promise 這玩意,是每一個面試官都會問的問題,可是你真的瞭解promise嗎?其實我也不瞭解,下面的內容都是我從掘金、知乎、《ECMAScript6入門》上看的博客文章等資料,而後總結的,畢竟本身寫一遍,更有助於理解,若有錯誤,請指出 ~javascript
在過去寫異步代碼都要靠回調函數,當異步操做依賴於其餘異步操做的返回值時,會出現一種現象,被程序員稱爲 「回調地獄」,好比這樣 :前端
// 假設咱們要請求用戶數據信息,它接收兩個回調,假設咱們要請求用戶數據信息,它接收兩個回調,successCallback 和 errCallback
function getUserInfo (successCallback, errCallback) {
$.ajax({
url : 'xxx',
method : 'get',
data : {
user_id : '123'
},
success : function(res) {
successCallback(res) // 請求成功,執行successCallback()回調
},
error : function(err) {
errCallback(err) // 請求失敗,執行errCallback()回調
}
})
}
複製代碼
騙我 ? 這哪裏複雜了,明明很簡單啊,說好的回調地獄呢 ? 不急,繼續看java
假設咱們拿到了用戶信息,可是咱們還要拿到該用戶的聊天列表,而後再拿到跟某一「陌生」男人的聊天記錄呢 ?程序員
// getUserInfo -> getConnectList -> getOneManConnect()
getUserInfo((res)=>{
getConnectList(res.user_id, (list)=>{
getOneManConnect(list.one_man_id, (message)=>{
console.log('這是我和某位老男人的聊天記錄')
}, (msg_err)=>{
console.log('獲取詳情失敗,別污衊我,我不跟老男人聊天')
})
}, (list_err)=>{
console.log('獲取列表失敗,我都不跟別人聊天')
})
}, (user_err)=>{
console.log('獲取用戶我的信息失敗')
})
複製代碼
大兄弟,刺激不,三層嵌套,再多來幾個嵌套,就是 「回調地獄」 了。這時候,promise來了。es6
阮一峯老師的《ECMAScript 6入門》裏對promise的含義是 : Promise 是異步編程的一種解決方案,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上說,Promise 是一個對象,從它能夠獲取異步操做的消息。Promise 提供統一的 API,各類異步操做均可以用一樣的方法進行處理。面試
簡單來講,Promise就是對異步的執行結果的描述對象。ajax
1 : 只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。
2 : 一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。
3 : Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected
複製代碼
// 定外賣就是一個Promise,Promise的意思就是承諾
// 咱們定完外賣,飯不會當即到咱們手中
// 這時候咱們和商家就要達成一個承諾
// 在將來,無論飯是作好了仍是燒糊了,都會給咱們一個答覆
function 定外賣(){
// Promise 接受兩個參數
// resolve: 異步事件成功時調用(菜燒好了)
// reject: 異步事件失敗時調用(菜燒糊了)
return new Promise((resolve, reject) => {
let result = 作飯()
// 下面商家給出承諾,無論燒沒燒好,都會告訴你
if (result == '菜燒好了')
// 商家給出了反饋
resolve('咱們的外賣正在給您派送了')
else
reject('很差意思,咱們菜燒糊了,您再等一會')
})
}
// 商家廚房作飯,模擬機率事件
function 作飯() {
return Math.random() > 0.5 ? '菜燒好了' : '菜燒糊了'
}
// 你在家上餓了麼定外賣
// 有一半的機率會把你的飯燒糊了
// 好在有承諾,他仍是會告訴你
定外賣()
// 菜燒好執行,返回'咱們的外賣正在給您派送了'
.then(res => console.log(res))
// 菜燒糊了執行,返回'很差意思,咱們菜燒糊了,您再等一會'
.catch(res => console.log(res))
複製代碼
Promise 對象是一個構造函數,用來生成一個Promise實例。npm
Promise構造函數接受一個函數做爲參數,這個函數有兩個參數,分別是resolve()和reject()。
resovle()函數是將Promise對象從pending變成fulfilled,在異步操做完成時執行,將異步結果,做爲參數傳遞出去。
reject()函數是將Promise對象從pending變成rejected,在異步執行失敗時執行,將報錯信息,做爲參數傳遞出去。
// 簡單的一個promise實例, 來自阮一峯老師的es6 示例代碼
const promise = new Promise((resolve, reject) => {
// some code
if(/* 異步執行成功 */) {
resolve(res)
} else {
reject(error)
}
})
複製代碼
Promise 有個.then()方法,then 方法中的回調在微任務隊列中執行,支持傳入兩個參數,一個是成功的回調,一個是失敗的回調,在 Promise 中調用了 resolve 方法,就會在 then 中執行成功的回調,調用了 reject 方法,就會在 then 中執行失敗的回調,成功的回調和失敗的回調只能執行一個,resolve 和 reject 方法調用時傳入的參數會傳遞給 then 方法中對應的回調函數。編程
// 執行 resolve
let promise = new Promise((resolve, reject) => {
console.log(1)
resolve(3)
})
console.log(2)
promise.then((data)=>{
console.log(data)
}, (err)=>{
console.log(err)
})
// 1
// 2
// 3
複製代碼
// 執行 reject
let promise = new Promise((resolve, reject) => {
console.log(1)
reject()
})
promise.then(()=>{
console.log(2)
}, ()=>{
console.log(3)
})
// 1
// 3
複製代碼
[ 注意 : then方法中的回調是異步的!!!]segmentfault
爲何上面第一個示例代碼的結果是 1 -> 2 -> 3呢 ?傳入Promise 中的執行函數是當即執行完的啊,爲何不是當即執行 then 中的回調呢?由於then 中的回調是異步執行,表示該回調是插入事件隊列末尾,在當前的同步任務結束以後,下次事件循環開始時執行隊列中的任務。
Promise 的回調函數不是正常的異步任務,而是微任務(microtask)。它們的區別在於,正常任務追加到下一輪事件循環,微任務追加到本輪事件循環。這意味着,微任務的執行時間必定早於正常任務
then方法的返回值是一個新的GPromise對象,這就是爲何promise可以進行鏈式操做的緣由。
then方法中的一個難點就是處理異步,經過setInterval來監聽GPromise對象的狀態改變,一旦改變,就是執行GPromise對應的then方法中相應的回調函數。這樣回調函數就可以插入事件隊列末尾,異步執行。
複製代碼
then有兩個參數 : onFulfilled 和 onRejected
· 當狀態state爲fulfilled,則執行onFulfilled,傳入this.value。當狀態state爲rejected,則執行onRejected,傳入this.reason
· onFulfilled,onRejected若是他們是函數,則必須分別在fulfilled,rejected後被調用,value或reason依次做爲他們的第一個參數
class Promise{
constructor(executor){...}
// then 方法 有兩個參數onFulfilled onRejected
then(onFulfilled,onRejected) {
// 狀態爲fulfilled,執行onFulfilled,傳入成功的值
if (this.state === 'fulfilled') {
onFulfilled(this.value);
};
// 狀態爲rejected,執行onRejected,傳入失敗的緣由
if (this.state === 'rejected') {
onRejected(this.reason);
};
}
}
複製代碼
因爲promise每次調用then方法就會返回一個新的promise對象,若是該then方法中執行的回調函數有返回值,那麼這個返回值就會做爲下一個promise實例的then方法回調的參數,若是 then 方法的返回值是一個 Promise 實例,那就返回一個新的 Promise 實例,將 then 返回的 Promise 實例執行後的結果做爲返回 Promise 實例回調的參數。
還記得剛開頭說的那個「陌生」男人例子嗎 ?這裏咱們用promise的鏈式操做重寫下
// 原來的代碼
getUserInfo((res)=>{
getConnectList(res.user_id, (list)=>{
getOneManConnect(list.one_man_id, (message)=>{
console.log('這是我和某位老男人的聊天記錄')
}, (msg_err)=>{
console.log('獲取詳情失敗,別污衊我,我不跟老男人聊天')
})
}, (list_err)=>{
console.log('獲取列表失敗,我都不跟別人聊天')
})
}, (user_err)=>{
console.log('獲取用戶我的信息失敗')
})
// Promise重寫的代碼
function handleAjax (params) {
return new Promise((resolve, reject)=>{
$.ajax({
url : params.url,
type : params.type || 'get',
data : params.data || '',
success : function(data) {
resolve(data)
},
error : function(error) {
reject(error)
}
})
})
}
const promise = handleAjax({
url : 'xxxx/user'
});
promise.then((data1)=>{
console.log('獲取我的信息成功') // 獲取我的信息成功
return handleAjax({
url : 'xxxx/user/connectlist',
data : data1.user_id
});
})
.then((data2)=>{
console.log('得到聊天列表')
return handleAjax({
url : 'xxxx/user/connectlist/one_man',
data : data2.one_man_id
});
})
.then((data3)=>{
console.log('得到跟某男人的聊天')
})
.catch((err)=>{
console.log(err)
})
複製代碼
Promise 實例具備then方法,也就是說,then方法是定義在原型對象Promise.prototype上的。它的做用是爲 Promise 實例添加狀態改變時的回調函數。前面說過,then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。
then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。所以能夠採用鏈式寫法,即then方法後面再調用另外一個then方法。
採用鏈式的then,能夠指定一組按照次序調用的回調函數。這時,前一個回調函數,有可能返回的仍是一個Promise對象(即有異步操做),這時後一個回調函數,就會等待該Promise對象的狀態發生變化,纔會被調用
Promise.prototype.catch方法是.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。Promise對象狀態變爲resolved,則會調用then方法指定的回調函數;若是異步操做拋出錯誤,狀態就會變爲rejected,就會調用catch方法指定的回調函數,處理這個錯誤。另外,then方法指定的回調函數,若是運行中拋出錯誤,也會被catch方法捕獲。
Promise 對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤老是會被下一個catch語句捕獲
通常來講,不要在then方法裏面定義 reject 狀態的回調函數(即then的第二個參數),老是使用catch方法。
Promise.all方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.all([p1, p2, p3])
複製代碼
Promise.all方法接受一個數組做爲參數,p一、p二、p3都是 Promise 實例,若是不是,就會先調用下面講到的Promise.resolve方法,將參數轉爲 Promise 實例,再進一步處理。
p的狀態由p一、p二、p3決定,分紅兩種狀況。
(1)只有p一、p二、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。
(2)只要p一、p二、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
複製代碼
Promise.race方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.all([p1, p2, p3])
複製代碼
上面代碼中,只要p一、p二、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數
Promise.race方法的參數與Promise.all方法同樣,若是不是 Promise 實例,就會先調用下面講到的Promise.resolve方法,將參數轉爲 Promise 實例,再進一步處理。
有時須要將現有對象轉爲 Promise 對象,Promise.resolve方法就起到這個做用
Promise.resolve('test')
// 等價於
new Promise(resolve => resolve('test'))
// 更多請看阮一峯老師的ES6 Promise對象
複製代碼
Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。
const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (err) {
console.log(err) // 出錯了
});
// 更多請看阮一峯老師的ES6 Promise對象
複製代碼
阮一峯 ES6 : es6.ruanyifeng.com/#docs/promi…
知乎例子 : zhuanlan.zhihu.com/p/29632791
掘金 卡姆愛卡姆 : juejin.im/post/5b2f02…
來自segmentfault 的GEEK做者 : segmentfault.com/a/119000001…
我的博客 : blog.pengdaokuan.cn:4001「