JavaScript深刻淺出異步編程2、promise原理

其實Promise自己並不具有異步的能力,而之因此這裏須要單獨開一篇說明其原理,是由於Promise異步編程的過程當中是一個不可或缺的一環。緣由下面細說。javascript

在說promise以前,有必要先說下JS中的回調方式。好比下面:java

function doSomethingAfterTime(time, something) {
    setTimeout(fun, time);
}
複製代碼

可是這樣的回調方式有一個問題,可讀性太差。另外當回調的層次多了之後,容易陷入回調地獄。舉個例子:面試

function func1(cb){
    // do something
    cb();
}
function func2(cb){
    // do something
    cb();
}
function func3(cb){
    // do something
    cb();
}
// do
func1(function(){
    func2(function(){
        func3(function(){

        });
    });
});
複製代碼

這樣的代碼讀起來簡直就是折磨,暈死!編程

下面試着改進下代碼,試着將回調函數封裝起來。順便剖析下promise的原理。數組

Promise的原理

先來一個最簡單的。promise

function Promise(something){
    var callback = null;
    this.then = function(onCompelete){
        callback = onCompelete;
    };
    something(function (value){
        callback(value);
    });
}
複製代碼

下面是調用代碼。bash

// 事件1
function func1(){
    return new Promise(function(resolve){
        // do something
        setTimeout(function(){
            console.log('func1');
            resolve();
        },1000);
    });
}

func1().then(function(){
    console.log('func1 complete');
});
複製代碼

上面對Promise的封裝算是最簡單的版本,只是模擬了Promise的調用方法,好比then,還有Promise的構造函數。可是這樣的封裝沒法實現鏈式調用,鏈式調用的核心就是當調用某個方法的時候返回該對象自己或者該對象對應class的全新對象。而對於Promise的改造也很簡單.架構

  1. then方法返回Promise自己
  2. Promoise須要支持多個callback
function Promise(something){
    var callbacks = [];
    this.then = function(onCompelete){
        callbacks.push(onCompelete);
        return this;
    };
    function resolve(value){
        callbacks.forEach(function(cb){
            cb(value);
        });
    }
    something(resolve);  
}
複製代碼

調用代碼以下:異步

func1().then(function(){
    console.log('func1 complete');
}).then(function(){
    console.log('then2');
}).then(function(){
    console.log('then3');
});
複製代碼

如今的Promise執行上面的代碼後可以獲得正確的執行結果,可是有一個問題,若是咱們想在then方法再調用一個返回promise的方法?好比這樣:異步編程

// 事件2
function func2(){
    return new Promise(function(resolve){
        // do something
        setTimeout(function(){
            console.log('func2');
            resolve();
        },1000);
    });
}

func1().then(func2).then(function(){
    console.log('all complete');
});
複製代碼

輸出以下:

func1
all complete
func2
複製代碼

你會發現雖然func2成功調用了,可是輸出順序亂了,咱們指望的正確輸出順序應該是:

func1
func2
all complete
複製代碼

分析下問題出在哪裏?問題就出在Promise中的callbacks,第一個then是在func1返回的Promise上調用的,而第二個then事實上仍是在func1返回的Promise上調用的。然而咱們但願的是,第二個then應該是在func2返回的Promise調用,這時候就須要考慮如何進一步改造Promise了。

對於then傳入的onCompelete函數參數,它是不知道這個函數具體是否會返回Promise,只有調用了onCompelete方法才能知道具體返回的數據。可是onCompelete是回調函數,你沒法直接在then中調用。所以須要考慮其餘的方式。

若是then方法裏面返回一個新的Promise對象呢?用這個新的Promise做爲中間代理,好比這樣:

function Promise(something){
    var callbacks = [];
    this.then = function(onCompelete){
        return new Promise(function (resolve) {
            callbacks.push({
                onCompelete: onCompelete,
                resolve: resolve
            });
        });
    };
    function resolve(value){
        callbacks.forEach(function(cb){
            var ret = cb.onCompelete(value);
            cb.resolve(ret);
        })
    }
    something(resolve);  
}
複製代碼

可是運行的時候你會發現輸出順序仍是沒變,仍是有問題的。那麼繼續分析問題出在哪裏? 經過調試發現,resolve傳入的value有多是promise對象,而咱們已經在then方法裏面返回了新的promise對象了,交由該對象做爲代理了。所以resolve傳入的value若是是promise對象的話,那麼就須要把當前promiseresolve處理權交出去,交給傳入的promise對象。至關於代理人把權力交還給實際應該處理的對象。可能有點繞,我再詳細的描述下

func1返回的promisep1then返回的promisep2resolve傳入的promise對象爲p3,func2返回的promise對象爲p4

上面一共提到4個promise對象。

說下描說下調用順序。

首先由func1建立p1,而後調用then方法建立了p2,而後再次調用了then方法,由p2建立了p3p2p3都是由then建立的代理人。

這時候func1中的異步代碼執行了,1秒事後由func1調用了p1resolve方法,而且將callbacks數組內的方法依次調用,而後由cb.onCompelete(value)方法間接獲得func2返回的p4,接着調用p2resolve方法將p4傳入。可是上面說了,p2只是個代理,應該把權力交還給p4來執行。這樣p4獲得權力--回調函數,當func2的異步代碼執行完畢後,由p4來執行回調函數。

所以resolve方法須要進行以下改造。

function resolve(value) {
    // 交還權力,而且把resolve傳過去
    if (value && (typeof value.then === 'function')) {
        value.then.call(value, resolve);
        return;
    }
    callbacks.forEach(function (cb) {
        var ret = cb.onCompelete(value);
        cb.resolve(ret);
    });
}
複製代碼

上面的代碼就是交權的代碼。這樣徹底的Promise修改以下:

function Promise(something) {
    var callbacks = [];
    this.then = function (onCompelete) {
        return new Promise(function (resolve) {
            callbacks.push({
                onCompelete: onCompelete,
                resolve: resolve
            });
        });
    };
    function resolve(value) {
        if (value && (typeof value.then === 'function')) {
            value.then.call(value, resolve);
            return;
        }
        callbacks.forEach(function (cb) {
            var ret = cb.onCompelete(value);
            cb.resolve(ret);
        });
    }
    something(resolve);
}
複製代碼

這樣修改事後,再執行以下代碼:

func1().then(func2).then(function () {
    console.log('all complete');
});
複製代碼

如今就能獲得正確的執行結果了。

至此,一個簡單的Promise定義完了。這時候有一個問題,若是調用then方法以前resolve已經被執行了怎麼辦呢,豈不是永遠都得不到回調了?好比這樣:

(new Promise(function (resolve) {
    resolve();
})).then(function(){
    console.log('complete');
});
複製代碼

你會發現then裏面的回調就不會執行了。其實這時候只須要作一個小小的改動就好了。改造以下:

function Promise(something) {
    var callbacks = [];
    this.then = function (onCompelete) {
        return new Promise(function (resolve) {
            callbacks.push({
                onCompelete: onCompelete,
                resolve: resolve
            });
        });
    };
    function resolve(value) {
        if (value && (typeof value.then === 'function')) {
            value.then.call(value, resolve);
            return;
        }
        setTimeout(function(){
            callbacks.forEach(function (cb) {
                var ret = cb.onCompelete(value);
                cb.resolve(ret);
            });
        },0);
    }
    something(resolve);
}
複製代碼

你會發現,這裏只是在resolve方法裏面,將執行的回調放入setTimeout中,而且timeout設爲0。這裏稍微說下原理

在第一篇中提到setTimeout相似定時器,JS內容在執行setTimeout的回調函數的時候使用線程調度的方式將回調函數調度到JS線程執行。但凡涉及到線程調度那麼確定須要等待JS線程空閒的時候才能調度過來。這時候將timeout設爲0,至關於改變了代碼執行順序。

在實際的開發過程當中,上面的Promise代碼仍是缺乏了一個功能,那就是狀態管理,好比:pendingfulfilledrejected。下面的代碼繼續加入狀態管理的代碼,先添加pendingfulfilled的狀態:

function Promise(something) {
    var callbacks = [];
    var state = 0;//0:pending,1:fulfilled
    var resultValue = null;
    this.then = function (onCompelete) {
        return new Promise(function (resolve) {
            handleCallBack({
                onCompelete: onCompelete,
                resolve: resolve
            });
        });
    };
    function handleCallBack(callback){
        switch(state){
            case 0:{
                callbacks.push(callback);
                break;
            }
            case 1:{
                var ret = callback.onCompelete(resultValue);
                callback.resolve(ret);
                break;
            }
            default:{
                break;
            }
        }
    }
    function resolve(value) {
        if (value && (typeof value.then === 'function')) {
            value.then.call(value, resolve);
            return;
        }
        state = 1;
        resultValue = value;
        setTimeout(function(){
            callbacks.forEach(function (cb) {
                handleCallBack(cb);
            });
        },0);
    }
    something(resolve);
}
複製代碼

下面再繼續加入reject功能。

function Promise(something) {
    var callbacks = [];
    var state = 0;//0:pending,1:fulfilled 2:reject
    var resultValue = null;
    this.then = function (onCompelete, onReject) {
        return new Promise(function (resolve) {
            handleCallBack({
                onCompelete: onCompelete,
                resolve: resolve,
                reject: onReject
            });
        });
    };
    function handleCallBack(callback) {
        switch (state) {
            case 0: {
                callbacks.push(callback);
                break;
            }
            case 1: {
                var ret = callback.onCompelete(resultValue);
                callback.resolve(ret);
                break;
            }
            case 2: {
                if(callback.reject){
                    var ret = callback.reject(resultValue);
                }
                callback.resolve(ret);
                break;
            }
            default: {
                break;
            }
        }
    }
    function reject(error) {
        state = 2;
        resultValue = error;
        setTimeout(function () {
            callbacks.forEach(function (cb) {
                handleCallBack(cb);
            });
        }, 0);
    }
    function resolve(value) {
        if (value && (typeof value.then === 'function')) {
            value.then.call(value, resolve);
            return;
        }
        state = 1;
        resultValue = value;
        setTimeout(function () {
            callbacks.forEach(function (cb) {
                handleCallBack(cb);
            });
        }, 0);
    }
    something(resolve,reject);
}
複製代碼

OK,經過上面一步一步對Promise進行修改,基本上是把Promise的功能完善了。

從這個上面一步一步剖析Promise原理的過程當中,咱們發現,Promise自己並不提供異步功能,Promise只是對函數的回調功能進行了封裝,甚至能夠理解爲Promise就是一個回調代理。可是正是有了這個回調代理,使得咱們的回調方式發生了完全的改變,甚至直接影響了項目的架構設計。而在平時的開發過程當中,Promise在異步編程中起到了幾乎不可替代的做用。

相關文章
相關標籤/搜索