ES6中promise如何實現

一本正經的扯淡

顧名思義,promise中文意思就是承諾,也就是如今實現不了未來•••••,可是未來這玩意誰說的準呢。就像你去泡妞,你可能許下各類諾言,但能不能實現,徹底取決於你這人靠不靠譜。好在計算機不是人,不是人,不是人,••••正由於不是人,因此它許下的承諾,它就必定會給你一個結果。
等待承諾實現的過程當中很漫長,因此你能夠作一些其它的事情,不必總是堵在這一條道上,也就是異步。打個比方,你打電話給飯店老闆叫了個外賣,老闆告訴你,10分鐘後送過去,也就是說老闆給了你一個承諾,因而你等啊等,這中間又去上了個廁所,玩了會手機••••••,這就是異步,老闆給的承諾並無妨礙你幹其它的事情。OK,扯淡結束。angularjs


1、promise這妞有啥好

爲了實現異步,通常要設置一個回調函數es6

setTimeout(function(){
    console.log(1);
    setTimeout(function(){
        console.log(2);
        setTimeout(function(){
            console.log(3);
            setTimeout(function(){
                console.log(4);
                setTimeout(function(){
                    console.log(5);
                },500)
            },400)
        },300)
    },200)
},100);

••••••有沒有一種想死的感受!
promise最大優點就是第一消滅了這種回調地獄,第二增長了錯誤捕獲,像下面這種,ajax

promis.then(function (response) {
    //do something;
}, function (reason) {
    //get error
}).then(function (response) {
    //do something;
}, function (reason) {
    //get error
}).then(function (response) {
    //do something;
}, function (reason) {
    //get error
});

若是不作錯誤處理則更清晰chrome

promis.then(function (response) {
    //do something;
}).then(function (response) {
    //do something;
}).then(function (response) {
    //do something;
}).then(function (response) {
    //do something;
});

它使得異步的代碼看起來像是在同步執行,大大加強了代碼的可讀性。
美中不足的是你得寫一堆的.then(function(){},function(){}),可是和回調地獄相比,忍了。
在ES7中會有號稱是異步的終極解決方案,async和await,那是後話。promise

2、這妞性格怎麼樣

前面說了,計算機不是人,因此它許下的承諾,它必定會給你一個結果,無論這個承諾的結果是接受仍是拒絕。
因此,第一,promise必定會返回一個結果。
第二,這個結果是不可逆的,你只能接受,本質是由於promise的狀態不可逆,一旦它變成了resolve或者reject,你休想再讓你變成pending,不然,它要會說話,確定回你的只有一個字,滾!
第3、promise的結果何時返回,你說了不算,你去泡妞的時候,妞也不必定當場就答應你吧,或許想個3、五天也說不定,這個主動權不是掌握在你手中的。
第4、ES6的promise執行過程當中,你是沒法得到執行的進度的,到底它如今是pending仍是resolve,仍是reject。就好像妞和她的閨蜜探討要不要接受你,你是打聽不到的。固然並非徹底不能,例如angularjs的$q實現一個notify方法,能夠獲取到執行進度的通知。
最後說一點兒你的權力,你能決定的是在何時去取promise的結果,也就是調用then方法的時間,就好像你天天追着妞問,你想好沒有••••••,妞這個時候會有三種回答,一是答應你,二是拒絕你,三是還得再想一想,XXXX時候再告訴你••••,也就說這TMD又是一個承諾•••••。
咳、咳,如今開始必須嚴肅點,畢竟技術是一件嚴肅的事情。瀏覽器

3、漂亮的妞,是個男人就會有想法

說白了,promise就是一個對象,一個只能經過then方法來取得它上面的值的對象。
在es6中,你只要大喊一句,妞,給我個承諾,它就會給你一個promise,就像下面這樣:閉包

var promise = new Promise(function(resolve,reject){
    //do something;
})

而後你就能夠調用它的then方法去取值,那麼從這個角度看,這個構造函數必定是返回了一個帶有then方法的對象。另外還有狀態,狀態的變化不可逆。再加上些其它的方法,如all、catch•••,不過不要着急,咱們一步一步來意淫出這個漂亮的妞••••
一、一般狀況,咱們使用回調一個函數內執行另一個函數:異步

function doSomething(callback){
    console.log("do something");
    callback();
}
doSomething(function(){
    console.log("a");
});

二、可是在使用promise時,咱們是用then方法去取結果,而promise就是個對象,那麼上面的代碼看起來應該這樣寫:async

function doSomething(){
    console.log("a");
    return {
        then: function(callback){
            var value = 1;
            callback(value);
        }
    }
}
doSomething().then(function(res){
    console.log(res);
});

在這裏,咱們調用dosomething函數時,返回了一個帶有then方法的對象,而後在then方法回調中去執行,如今看來是否是有那麼點樣子了,時刻記得兩件事,對象, then方法。
三、在ES6中Promise是一個構造函數,這簡單,給這個dosomething換個名字,函數

function Promise(){
    this.then = function(callback){
        var value = 1;
        callback(value);
    }
}

在實例化promise的時候,要傳一個函數進去,這也簡單

function Promise(fn){
    this.then = function(callback){
        callback();
    }
}

實例化傳入的函數fn中(下文中的fn都是指代這個匿名函數),你會傳入2個參數,一個叫resolve,另外一個叫reject,爲了簡單起見,咱們不考慮reject,它的道理和resolve是同樣的。那麼就像這樣:

var promise = new Promise(function(resolve){
    var value = 1;
    resolve(value);
})

即然傳了一個fn函數進去,那麼在實例化過程當中,這個函數必定會在某個時刻執行。執行時,它又會接收到一個參數resolve,這個resolve必定是一個函數,這點從上面就能夠很明顯的看出來,resolve在實例化時執行了,並且接收到了一個參數,在這裏是變量value。那麼Promise函數內部極可能是這樣:

function Promise(fn){
        function resolve(value){}
        this.then = function (onResolved) {};
        fn(resolve);
  }

爲了看起來更直接,這裏咱們把調用then方法傳的第一個函數就叫作onResolved,那麼接下來咱們應該考慮在實例化的時候,還有什麼事情要作,在then方法的回調函數中咱們但願獲得promise的值,這個值是在fn函數調用後被resolve函數運算後獲得的,最終要在onResolved函數中拿到,也就是說,咱們必須在resolve中將這個值傳遞給onResolved,迂迴一下:

function Promise(fn) {
  var callback = null;
  function resolve(value) {
      callback(value);
  }
  this.then = function(onResolved) {
    callback = onResolved;
  };
  fn(resolve);
}

可是這裏有一個問題,就是咱們調用resolve方法時,尚未調用過then方法,所以callbak是null,瀏覽器報錯:callback is not a function,這裏hack下,讓resolve方法的執行在then以後。

function Promise(fn) {
        var callback = null;
        function resolve(value) {
            setTimeout(function(){
                callback(value);
            },0)
        }
        this.then = function(onResolved) {
            callback = onResolved;
        };
        fn(resolve);
    }

執行一下,

var promise = new Promise(function(res){
    var value = 2;
    res(2);
});
promise.then(function(res){
    console.log(res);
})

OK,成功的輸出。目前爲止,promise的輪廓算是被咱們意淫出來了。
四、promise是有狀態的,並且狀態不可逆,一樣的爲了簡單起見,我先來搞定從pending變到resolved,那麼rejected也同樣。仔細想下,執行了resolve方法後能夠獲得一個resolved狀態的值,那麼必然在resolve方法中會去改變promise的狀態,而且獲得這個值,那麼代碼貌似應該這樣寫:

function Promise(fn) {
    var state = 'pending';
    function resolve(newValue) {
        state = 'resolved';
        callback(newValue);
    }
    this.then = function(onResolved) {
        callback = onResolved;
   };
    fn(resolve);
 }

這裏咱們先把setTimeout這傢伙給幹掉了,由於咱們加入了狀態,也就意味咱們是想經過狀態的變化來知道能不能獲得值,那麼問題來了,咱們不知道狀態啥時候變,就像你不知道你要泡的妞啥時候答應你同樣,你只能追問,萬一妞沒想好,她極可能再給你一個承諾,就是那個該死的XXX時候再告訴你,不過好歹她也算給了你一個等待的機會,而咱們如今要作的就是創造這麼個機會。

function Promise(fn) {
  var state = 'pending';
  var value;
  var deferred;
  function resolve(newValue) {
    value = newValue;
    state = 'resolved';
    if(deferred) {
      handle(deferred);
    }
  }
  function handle(onResolved) {
    if(state === 'pending') {
      deferred = onResolved;
      return;
    }
    onResolved(value);
  }
  this.then = function(onResolved) {
    handle(onResolved);
  };
  fn(resolve);
}

這裏引入了另一個函數handle,至此能夠說promise的最關鍵的東西咱們已經看到了,妞的身材逐漸顯現。又扯遠了•••••
仔細看下除了handle咱們還引入兩個變量value和deferred,先從最簡單的來:
value的做用很簡單,在構造函數內它是一個全局變量,起到一個橋樑做用,就是爲了在handle函數內能取到newValue的值,而newValue就是fn函數裏的那個結果。
handle咱們估且能夠認爲它是妞的一個管家,它會去替咱們詢問妞有沒有想好,也就是去判斷當前這個承諾的狀態,再決定怎麼作。
deferred咱們估且能夠這樣理解,它就是管家的一個記事本,你隔三差五的去問,它老人家不得記下來,若是一不當心忘了,那就悲催了。
這下無論是同步仍是異步,咱們隨時能夠在then方法中去取值,若是值沒有被resolve,也就是說狀態沒發生變化,deferred將給咱們記錄下這件事,等到resolve的那個時間點把值傳給then方法中那個回調函數,onResolved。
在這裏請默唸一百遍handle,defer,再接着往下看,我保證他們會讓你困惑。
五、回到最初,爲何要用promise,想一想回調地獄,再想一想promise是怎麼解決的,那就是then方法鏈式調用。
可以實現鏈式調用,也就是說then方法返回的值也必定是個promise,這樣你才能.then,.then的一直寫下去。廢話不說,沒代碼說個毛:

function Promise(fn) {
  var state = 'pending';
  var value;
  var deferred = null;

  function resolve(newValue) {
    value = newValue;
    state = 'resolved';
    if(deferred) {
      handle(deferred);
    }
  }

  function handle(handler) {
    if(state === 'pending') {
      deferred = handler;
      return;
    }
    if(!handler.onResolved) {
      handler.resolve(value);
      return;
    }
    var ret = handler.onResolved(value);
    handler.resolve(ret);
  }

  this.then = function(onResolved) {
    return new Promise(function(resolve) {
      handle({
        onResolved: onResolved,
        resolve: resolve
      });
    });
  };
  fn(resolve);
}

這下換個姿式,咱們先啃硬貨。咱們讓then方法返回了一個promise,並且這個promise實例化時傳入的函數裏調用了handle函數,傳入了一個對象,onResolved很顯然就是then方法裏第一個函數,沒什麼可說的。關鍵是這handle和resolve是哪一個?思考1分鐘。
這裏咱們用setTimeout簡單模擬一個異步,拿一個then看下,發生了什麼:

var promise = new Promise(function(resolve){
    setTimeout(function(){
        resolve(1);
    },3000)
});
promise.then(function(res){
    console.log(res);
})

首先咱們去new一個promise,在實例化的過程當中,調用了傳進的那個函數,3秒後才能執行到resolve,緊接着調用了它的then方法,這個時候因爲promise的狀態沒變,確定取不到值,好在then方法會返回個promise,因而又執行了一次promise的實例化過程。這裏沒法迴避的就是做用域的問題,這個關係到handle函數執行在哪一個環境中,參數的到底從哪一個地方獲取到,另外就是強大的閉包。相關知識不解釋。
爲了看的更清楚,咱們加入一些標記,到chrome的控制檯中調試下:

var count = 0;
function Promise(fn) {
    var state = 'pending';
    var value;
    var deferred = null;
    var scope = ++count;
    function resolve(newValue) {
        value = newValue;
        state = 'resolved';
        console.log("resolve: I am in " +scope);
        if(deferred) {
            handle(deferred);
        }
    }
    function handle(handler) {
        console.log("handle: I am in " +scope);
        if(state === 'pending') {
            deferred = handler;
            return;
        }
        if(!handler.onResolved) {
            handler.resolve(value);
            return;
        }
        var ret = handler.onResolved(value);
        handler.resolve(ret);
    }
    this.then = function(onResolved) {
        console.log("then: I am in " + scope);
        return new Promise(function(resolve) {
            console.log("then promise: I am in " + scope);
            handle({
                onResolved: onResolved,
                resolve: resolve
            });
        });
    };
    fn(resolve);
}
var promise = new Promise(function(resolve){
    setTimeout(function(){
        resolve(1);
    },3000)
});
promise.then(function(res){
    console.log(res);
});

加入的scope是爲了監視做用域的變化,以間接反應出咱們調用handle時是在哪一個做用域上查詢到的,此外咱們還須要監視state和deferred的變化。
主要看then調用以後,廢話不說上圖:
5-一、在執行then方法的時候,scope=1,state,deferred不可用。因爲模擬了異步,這個時候第一個promise的resolve方法並無執行,這裏模擬了3秒,實際狀況下,好比ajax取數據時,咱們並不知道這個準確的時間,就像開始時說的,這妞啥時候答應你,主動權不在你手中,由妞說了算。
圖片描述
5-二、接下來去實例化then方法建立的這個promise,scope = 2,state=」pending」,deferred=null。
圖片描述
5-三、在實例化完成以後,此時去執行fn函數,scope=1,state,deferred不可用。
圖片描述
第一,函數的做用域是在定義時就生成的,而不是在調用的時候。第二個promise定義的時候,是在第一個promise做用域上,這樣即便它被return了出去,因爲閉包的特性,仍讀取的是第一個做用域上值,因此這裏的handle一定是第一個promise的handle。而resolve則不一樣,它是做爲行參傳遞了進來,因此這裏的resolve是第二個promise的resolve。
5-四、進入handle時,scope = 1,state =」 pending」,deferred保存了參數。
圖片描述
5-五、3秒時間到,第一個promise裏的resolve被執行了,也就是說拿到告終果,這時候,scope=1,state = 「resolved」,deferred保存着剛纔傳進來的那個對象,再次進入handle函數。
圖片描述
5-六、scope=1,state = 「resolved」,deferred求值爲true,所以確定會繼續執行。下面添加的這段代碼在這裏也就很清楚了,假如then方法中沒有傳進來的onResolved函數,這裏的value將直接交給下一個then方法中的onResolved函數使用,避免一些無聊的人像這樣去調用:

promise.then().then().then(function(res){
    console.log(res);
})

正常人都會讓value在onResolved函數中接收到,而後ret就是onResolved函數的返回值,這裏沒有return回的值,因此ret確定是undefined。
圖片描述
5-七、scope=2,state = 「resolved」,deferred=null。這裏的resolve是第個promise的resolve,因此定義的時候就是在做用域2上,若是後面再調用then方法,生成新的promise,這時就會將undefined做爲第二個promise的值傳遞下去。
圖片描述
這裏再次強調一下,handle方法和deferred是核心所在,其背後的精髓無非仍是做用域和閉包的巧妙設計。變量的讀取一定先從自身所處做用域開始,若是自身做用域上讀不到,纔會一級一級向上訪問。
六、意淫到這裏基本上核心的東西就差很少了,下面咱們來加上reject時的狀況,直接上代碼:

function Promise(fn) {
    var state = 'pending';
    var value;
    var deferred;

    this.then = function (onResolved, onRejected) {
        return new Promise(function (resolve, reject) {
            handle({
                onResolved: onResolved,
                onRejected: onRejected,
                resolve: resolve,
                reject: reject
            });
        });
    };

    function resolve(newValue) {
        if (newValue && typeof newValue.then === 'function') {
            newValue.then(resolve);
            return;
        }
        state = 'resolved';
        value = newValue;

        if (deferred) {
            handle(deferred);
        }
    }

    function reject(reason) {
        state = 'rejected';
        value = reason;

        if (deferred) {
            handle(deferred);
        }
    }

    function handle(handler) {
        if (state === 'pending') {
            deferred = handler;
            return;
        }
        var handlerCallback;
        if (state === 'resolved') {
            handlerCallback = handler.onResolved;
        } else {
            handlerCallback = handler.onRejected;
        }
        if (!handlerCallback) {
            if (state === 'resolved') {
                handler.resolve(value);
            } else {
                handler.reject(value);
            }
            return;
        }
        var ret;
        try {
            ret = handlerCallback(value);
        } catch (e) {
            handler.reject(e);
            return;
        }
        handler.resolve(ret);
    }

    fn(resolve);
}

狀況基本和resolve是同樣的,resolve函數中加的if判斷只爲了對付返回值是promise的狀況下仍然能夠經過後續的then方法取到值,handle中的try/catch塊的加入使得能夠捕獲到promise及then方法回調中的錯誤,至於then方法的改變,看不懂的話自宮吧,你是女人當我沒說。

4、其它

固然這個promise只是一個基本的實現,依然很脆弱,但基本上能夠說有了一輪廓,剩下的部位各位看官本身添加,好比promise的all ,race,catch等。某種意義上說,它們也只是then方法的語法糖。
http://www.mattgreer.org/arti...,本文代碼出處,算是學習後的一點體會,水平有限,理解不對之處,還請指正。

相關文章
相關標籤/搜索