細說Promise

1、前言

JavaScript是單線程的,固,一次只能執行一個任務,當有一個任務耗時很長時,後面的任務就必須等待。那麼,有什麼辦法,能夠解決這類問題呢?(拋開WebWorker不談),那就是讓代碼異步執行嘛。什麼意思,如Ajax異步請求時,就是經過不斷監聽readyState的值,以肯定執行指定的回調函數。javascript

一般的異步執行有三種,回調函數、事件監聽以及發佈訂閱,其中事件監聽和發佈訂閱其實差很少,只是後者更加健壯一些。html

如回調函數,回調函數是應用在異步執行中最簡單的編程思想。以下:java

function async(item,callback){
    console.log(item);
    setTimeout(function(){
        callback(item+1);
    },1000);    
}

在上述列子中,執行async函數時,完成打印操做,並在1秒後執行callback回調函數(但不必定是1秒,詳情見」setTimeout那些事兒」)。es6

異步的主要目的就是處理非阻塞,提高性能。想象一下,若是某個操做須要通過多個async函數操做呢,以下:chrome

async(1, function(item){
    async(item, function(item){
        async(item, function(item){
            console.log('To be continued..');
        });
    });
});

是否是有點不易閱讀了?編程

再好比,爲了讓上述代碼更加健壯,咱們能夠加入異常捕獲。在異步的方式下,異常處理分佈在不一樣的回調函數中,咱們沒法在調用的時候經過try…catch的方式來處理異常, 因此很難作到有效,清楚。數組

哎喲喂,那可怎麼辦呢?promise

噔噔噔噔,噔噔噔噔—Promise閃亮登場。app

假若用ES6的Promise優化上述代碼,可得:異步

function opration(item){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            resolve(item+1);
        },1000);
    });
    console.log(item);
    return p;
}
function failed(e){
    console.log(e);
}
Promise.resolve(1).then(opration).then(opration).then(opration).catch(failed);

Promise 優化後的代碼,優勢顯而易見,讓回調函數變成了鏈式調用,避免了層層嵌套,使程序流程變得清晰明朗,併爲一個或者多個回調函數拋出的錯誤經過catch方法進行統一處理。

哎呦,不錯嘛,那這個ES6中的Promise究竟是何方聖神,具體使用法則是什麼呢?咱們就一塊兒來探究探究。

該篇博客原文地址:http://www.cnblogs.com/giggle/p/5575157.html

2、Promise概述

Promise是異步編程的一種解決方案,比傳統的解決方案(回調和事件)更合理和更強大。它由社區最先提出和實現,ES6將其寫進了語言標準,統一了用法,原生提供了Promise對象。

Promise對象有且只有三種狀態:

  一、 pending:異步操做未完成。

  二、 resolved:異步操做已完成。

  三、 rejected:異步操做失敗。

又,這三種狀態的變化只有兩種模式,而且一旦狀態改變,就不會再變:

  一、異步操做從pending到resolved;

  二、異步操做從pending到rejected;

好了,既然它是屬於ES6規範,咱們再經過chrome,直接打印出Promise,看看這玩意:

 

恩,一目瞭然,Promise爲構造函數,歐克,這樣經過它,咱們就能夠實例化本身的Promise對象了,並加以利用。

3、Promise入門指南

Promise既然是一個構造函數,那麼咱們就先new一個看看。以下:

var p = new Promise();

並執行上述代碼,chrome截圖以下:

怎麼報錯了呢?

哦,對了,Promise構造函數,須要一個函數做爲其參數哦,而且做爲參數的函數中,有兩個參數,第一個參數的做用爲,當異步操做從pending到resolved時,供其調用;第二個參數的做用爲,當異步操做從pending到rejected時,供其調用。

例如,我將匿名函數的第一個參數取名爲resolve;第二個參數取名爲reject。Demo以下:

var p = new Promise(function(resolve, reject){
    console.log('new一個Promise對象');
    setTimeout(function(){
        resolve('Monkey');
    },1000);
});

並執行上述代碼,chrome截圖以下:

特別提醒:當傳入匿名函數做爲構造函數Promise的參數時,咱們在new的時候,匿名函數就已經執行了,如上圖。

咦,上述代碼中,咱們在匿名函數中,經過setTimeout定時器,在1秒後,還調用了resolve呢,怎麼沒有報undefined或者錯誤呢?!

這就是Promise強大之處的一點。正由於這樣,咱們就能夠將異步操做改寫成優雅的鏈式調用。怎麼調用呢?

還記得,咱們在「Promise概述」一小節中,經過chrome打印Promise,用紅線框中的區域麼?其中,Promise原型中有一then方法(Promise.prototype.then),經過這個then方法,就能夠了。以下:

p.then(function(value){
    console.log(value);
});

其中,then方法有兩個匿名函數做爲其參數,與Promise的resolve和reject參數一一對應。執行代碼,結果以下:

好了,當then執行完後,若是咱們想繼續在其以後看,使用then方法鏈式調用,有兩種狀況,一種是直接返回非Promise對象的結果;另外一種是返回Promise對象的結果。

一、返回非Promise對象的結果:緊跟着的then方法,resolve馬上執行。並可以使用前一個then方法返回的結果。以下:

p.then(function(value){
    console.log(value);
    //返回非Promise對象,如個人對象
    return {
        name: 'Dorie',
        age: 18
    };
}).then(function(obj){
    console.log(obj.name);
});

執行上述完整代碼,chrome截圖以下:

 

二、返回Promise對象的結果:緊跟着的then方法,與new Promise後的then方法同樣,需等待前面的異步執行完後,resolve方可被執行。以下:

p.then(function(value){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            var message = value + ' V Dorie'
            resolve(message);
        },1000);
    });
    console.log(value);
    //返回一個Promise對象
    return p;
}).then(function(value){
    console.log(value);
});

執行上述完整代碼,chrome截圖以下:

 

那麼,當建立、執行Promise方法中有異常報錯,如何捕獲呢?

Promise.prototype.catch原型方法,就是爲其而設定的。它具備冒泡的特性,好比當建立Promise實例時,就出錯了,錯誤消息就會經過鏈式調用的這條鏈,一直追溯到catch方法,若是找到盡頭都沒有,就報錯,而且再找到catch以前的全部then方法都不能執行了。Demo以下(代碼太長,請自行展開):

<!DOCTYPE html>
    <head>
        <title>test</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
    <body>
        <script>
            var p = new Promise(function(resolve, reject){
                //M未定義
                console.log(M);
                setTimeout(function(){
                    resolve('Monkey');
                },1000);
            });
            p.then(function(value){
                var p = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        var message = value + ' V Dorie'
                        resolve(message);
                    },1000);
                });
                console.log(value);
                //返回一個Promise對象
                return p;
            }).then(function(value){
                console.log(value);
                return 'next is catch';
            }).catch(function(e){
                console.log(e);
            }).then(function(value){
                console.log('execute,but value is ' + value);
            });
        </script>
    </body>
</html>
View Code

執行上述代碼,chrome截圖以下:

 

好了,到這裏,咱們已經瞭解了最經常使用的Promise.prototype.then和Promise.prototype.catch這兩個原型方法。另外,像Promise構造函數還有屬於自身的方法,如all、rece、resolve、reject等,詳情請點擊這裏(here)。

經過一路上對Promise的講述,咱們也有了必定的認識,其實Promise並無想象中的那麼難以理解嘛。懂得Promise概念後,其實咱們本身也能夠實現一個簡易版的Promise。下面就一同嘗試實現一個唄。

4、模擬Promise

假設:有三個異步操做方法f1,f2,f3,且f2依賴於f1,f3依賴於f2。若是,咱們採用ES6中Promise鏈式調用的思想,咱們能夠將程序編寫成這樣:

f1().then(f2).then(f3);

那麼,經過上面這一系列鏈式調用,怎樣才能達到與ES6中Promise類似的功能呢?

初步想法:首先將上述鏈式調用的f二、f3保存到f1中,當f1中的異步執行完後,再調用執行f2,並將f1中的f3保存到f2中,最後,等f2中的異步執行完畢後,調用執行f3。詳細構思圖,以下:

 

從上圖可知,因爲f一、f2 、f3是可變得,因此存儲數組隊列thens,可放入,咱們即將建立的模擬Promise構造函數中。具體實現代碼以下:

//模擬Promise
function Promise(){
    this.thens = [];
};
Promise.prototype = {
    constructor: Promise,
    then: function(callback){
        this.thens.push(callback);
        return this;        
    }
};

而且,須要一個Promise.prototype.resolve原型方法,來實現:當f1異步執行完後,執行緊接着f1後then中的f2方法,並將後續then中方法,嫁接到f2中,如f3。具體實現代碼以下:

//模擬Promise,增長resolve原型方法
function Promise(){
    this.thens = [];
};
Promise.prototype = {
    constructor: Promise,
    then: function(callback){
        this.thens.push(callback);
        return this;        
    },
    resolve: function(){
        var t = this.thens.shift(), 
            p;
        if(t){
            p = t.apply(null,arguments);
            if(p instanceof Promise){
                p.thens = this.thens;
            }
        }
    }
};

測試代碼(代碼太長,自行打開並運行)。

function f1() {
    var promise = new Promise();
    setTimeout(function () {
       
        console.log(1);
        promise.resolve();
    }, 1500)

    return promise;
}

function f2() {
    var promise = new Promise();
    setTimeout(function () {
        console.log(2);
        promise.resolve();
    }, 1500);
    return promise;
}

function f3() {
    var promise = new Promise();
    setTimeout(function () {

        console.log(3);
        promise.resolve();
    }, 1500)

    return promise;
}
f1().then(f2).then(f3);
測試代碼

仔細品味,上述實現的Promise.prototype.resolve方法還不夠完美,由於它只可以知足於f一、f二、f3等方法都是使用模擬的Promise異步執行的狀況。而,當其中有不是返回的Promise對象的呢,而是返回一個數字,亦或是什麼也不返回,該怎麼辦?因此,針對以上提出的種種可能,再次改進resolve。改善代碼以下:

//模擬Promise,改善resolve原型方法
var Promise = function () {
    this.thens = [];
};
Promise.prototype = {
    constructor: Promise,
    then: function(callback){
        this.thens.push(callback);
        return this;        
    },
    resolve: function () {
        var t,p;
        t = this.thens.shift();
        t && (p = t.apply(null, arguments));
        while(t && !(p instanceof Promise)){
            t = this.thens.shift();
            t && (p = t.call(null, p));    
        }
        if(this.thens.length){
            p.thens = this.thens;
        };
    }
}

測試代碼(代碼太長,自行打開並運行)。

function f1() {
    var promise = new Promise();
    setTimeout(function () {
       
        console.log(1);
        promise.resolve();
    }, 1500)

    return promise;
}

function f2() {
    var promise = new Promise();
    setTimeout(function () {
        console.log(2);
        promise.resolve();
    }, 1500);
    return promise;
}

function f3() {
    var promise = new Promise();
    setTimeout(function () {

        console.log(3);
        promise.resolve();
    }, 1500)

    return promise;
}

function f4() {
    console.log(4);
    return 11;
}

function f5(x) {
    console.log(x+1);
}

function f6() {
    var promise = new Promise();
    setTimeout(function () {

        console.log(6);
        promise.resolve();
    }, 1500)

    return promise;
}

function f7() {
    console.log(7);
}

var that = f1().then(f2).then(f3).then(f4).then(f5).then(f6).then(f7);
測試代碼

好了,初步模擬的Promise就OK啦。

吼吼,對於Promise,咱們這一路走來,發現原來也不過如此呢。

5、拓展閱讀

[1]、再談Event Loop

[2]、MDN

[3]、大白話講解Promise(一)

[4]、當耐特

[5]、Promise & Deferred objects in JavaScript Pt.1

相關文章
相關標籤/搜索