什么是Promise?本代碼用定外賣來舉例子,讓你明白。
// 定外賣就是一個Promise,Promist的意思就是承諾
// 我們定完外賣,飯不會立即到我們手中
// 這時候我們和商家就要達成一個承諾
// 在未來,不管飯是做好了還是燒糊了,都會給我們一個答復
function orderFood(){
// Promise 接受兩個參數
// resolve: 異步事件成功時調用(菜燒好了)
// reject: 異步事件失敗時調用(菜燒糊了)
return new Promise((resolve, reject) => {
let result
console.log('in promise')
setTimeout(()=>{
// 商家廚房做飯,模擬概率事件
result = Math.random() > 0.5 ? '菜燒好了' : '菜燒糊了'
// 下面商家給出承諾,不管燒沒燒好,都會告訴你
if (result == '菜燒好了'){
// 商家給出了反饋
resolve('我們的外賣正在給您派送了')
}else{
reject('不好意思,我們菜燒糊了,您再等一會')
}
}, 5000)
})
}
// 你在家上餓了么定外賣
// 有一半的概率會把你的飯燒糊了
// 好在有承諾,他還是會告訴你
// 菜燒好執行,返回'我們的外賣正在給您派送了'
console.log('before order')
orderFood().then(res => console.log(res))
// 菜燒糊了執行,返回'不好意思,我們菜燒糊了,您再等一會'
.catch(res => console.log(res))
console.log('after order')
為了讓大家更容易理解,我們從一個場景開始講解,讓大家一步一步跟着思路思考,相信你一定會更容易看懂。
考慮下面一種獲取用戶id的請求處理
//例1
function getUserId() {
return new Promise(function(resolve) {
//異步請求
http.get(url, function(results) {
resolve(results.id)
})
})
}
getUserId().then(function(id) {
//一些處理
})
getUserId
方法返回一個promise
,可以通過它的then
方法注冊(注意注冊
這個詞)在promise
異步操作成功時執行的回調。這種執行方式,使得異步調用變得十分順手。
原理剖析
那么類似這種功能的Promise
怎么實現呢?其實按照上面一句話,實現一個最基礎的雛形還是很easy的。
極簡promise雛形
function Promise(fn) {
var value = null,
callbacks = []; //callbacks為數組,因為可能同時有很多個回調
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
上述代碼很簡單,大致的邏輯是這樣的:
- 調用
then
方法,將想要在Promise
異步操作成功時執行的回調放入callbacks
隊列,其實也就是注冊回調函數,可以向觀察者模式方向思考; - 創建
Promise
實例時傳入的函數會被賦予一個函數類型的參數,即resolve
,它接收一個參數value,代表異步操作返回的結果,當一步操作執行成功后,用戶會調用resolve
方法,這時候其實真正執行的操作是將callbacks
隊列中的回調一一執行;
可以結合例1
中的代碼來看,首先new Promise
時,傳給promise
的函數發送異步請求,接着調用promise
對象的then
屬性,注冊請求成功的回調函數,然后當異步請求發送成功時,調用resolve(results.id)
方法, 該方法執行then
方法注冊的回調數組。
相信仔細的人應該可以看出來,then
方法應該能夠鏈式調用,但是上面的最基礎簡單的版本顯然無法支持鏈式調用。想讓then
方法支持鏈式調用,其實也是很簡單的:
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
return this;
};
see?只要簡單一句話就可以實現類似下面的鏈式調用:
// 例2
getUserId().then(function (id) {
// 一些處理
}).then(function (id) {
// 一些處理
});
加入延時機制
細心的同學應該發現,上述代碼可能還存在一個問題:如果在then
方法注冊回調之前,resolve
函數就執行了,怎么辦?比如promise
內部的函數是同步函數:
// 例3
function getUserId() {
return new Promise(function (resolve) {
resolve(9876);
});
}
getUserId().then(function (id) {
// 一些處理
});
這顯然是不允許的,Promises/A+
規范明確要求回調需要通過異步方式執行,用以保證一致可靠的執行順序。因此我們要加入一些處理,保證在resolve
執行之前,then
方法已經注冊完所有的回調。我們可以這樣改造下resolve
函數:
function resolve(value) {
setTimeout(function() {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0)
}
上述代碼的思路也很簡單,就是通過setTimeout
機制,將resolve
中執行回調的邏輯放置到JS
任務隊列末尾,以保證在resolve
執行時,then
方法的回調函數已經注冊完成.
總結
剛開始看promise源碼的時候總不能很好的理解then和resolve函數的運行機理,但是如果你靜下心來,反過來根據執行promise時的邏輯來推演,就不難理解了。這里一定要注意的點是:promise里面的then函數僅僅是注冊了后續需要執行的代碼,真正的執行是在resolve方法里面執行的,理清了這層,再來分析源碼會省力的多。
現在回顧下Promise的實現過程,其主要使用了設計模式中的觀察者模式:
- 通過Promise.prototype.then和Promise.prototype.catch方法將觀察者方法注冊到被觀察者Promise對象中,同時返回一個新的Promise對象,以便可以鏈式調用。
- 被觀察者管理內部pending、fulfilled和rejected的狀態轉變,同時通過構造函數中傳遞的resolve和reject方法以主動觸發狀態轉變和通知觀察者。