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怎么實現呢?其實按照上面一句話,實現一個最基礎的雛形還是很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);
}

 

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

 

  1. 調用then方法,將想要在Promise異步操作成功時執行的回調放入callbacks隊列,其實也就是注冊回調函數,可以向觀察者模式方向思考;
  2. 創建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的實現過程,其主要使用了設計模式中的觀察者模式:

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM