10分鐘,讓你完全明白Promise原理

什麼是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


原理剖析

 

那麼相似這種功能的Promise怎麼實現呢?其實按照上面一句話,實現一個最基礎的雛形仍是很easy的。dom

 

極簡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);
}

 

上述代碼很簡單,大體的邏輯是這樣的:異步

 

  1. 調用then方法,將想要在Promise異步操做成功時執行的回調放入callbacks隊列,其實也就是註冊回調函數,能夠向觀察者模式方向思考;
  2. 建立Promise實例時傳入的函數會被賦予一個函數類型的參數,即resolve,它接收一個參數value,表明異步操做返回的結果,當一步操做執行成功後,用戶會調用resolve方法,這時候其實真正執行的操做是將callbacks隊列中的回調一一執行;

 

能夠結合例1中的代碼來看,首先new Promise時,傳給promise的函數發送異步請求,接着調用promise對象的then屬性,註冊請求成功的回調函數,而後當異步請求發送成功時,調用resolve(results.id)方法, 該方法執行then方法註冊的回調數組。函數

 

相信仔細的人應該能夠看出來,then方法應該可以鏈式調用,可是上面的最基礎簡單的版本顯然沒法支持鏈式調用。想讓then方法支持鏈式調用,其實也是很簡單的:this

this.then = function (onFulfilled) {
    callbacks.push(onFulfilled);
    return this;
};

 

see?只要簡單一句話就能夠實現相似下面的鏈式調用:url

 

// 例2
getUserId().then(function (id) {
    // 一些處理
}).then(function (id) {
    // 一些處理
});

 


加入延時機制

 

細心的同窗應該發現,上述代碼可能還存在一個問題:若是在then方法註冊回調以前,resolve函數就執行了,怎麼辦?好比promise內部的函數是同步函數:prototype

 

// 例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的實現過程,其主要使用了設計模式中的觀察者模式:

 

  1. 經過Promise.prototype.then和Promise.prototype.catch方法將觀察者方法註冊到被觀察者Promise對象中,同時返回一個新的Promise對象,以即可以鏈式調用。
  2. 被觀察者管理內部pending、fulfilled和rejected的狀態轉變,同時經過構造函數中傳遞的resolve和reject方法以主動觸發狀態轉變和通知觀察者。
相關文章
相關標籤/搜索