首頁  >  文章  >  web前端  >  詳細介紹JavaScript 中的 Promise

詳細介紹JavaScript 中的 Promise

黄舟
黄舟原創
2017-03-04 15:30:151450瀏覽

一、前言

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

通常的非同步執行有三種,回呼函數、事件監聽以及發布訂閱,其中事件監聽和發布訂閱其實差不多,只是後者更加健壯一些。

如回呼函數,回呼函數是應用在非同步執行中最簡單的程式設計想法。如下:

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

在上述列子中,執行async函數時,完成列印操作,並在1秒後執行callback回呼函數(但不一定是1秒,詳情請見”setTimeout那些事兒”)。

非同步的主要目的就是處理非阻塞,提升效能。想像一下,如果某個操作需要經過多個async函數操作呢,如下:

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

是不是有點不易閱讀了?

再比如,為了讓上述程式碼更加健壯,我們可以加入異常捕獲。在非同步的方式下,異常處理分佈在不同的回呼函數中,我們無法在呼叫的時候透過try…catch的方式來處理異常, 所以很難做到有效,清楚。

哎喲餵,那可怎麼辦呢?

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

倘若用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到底是何方聖神,具體使用法則是什麼呢?我們就一起來探究探究。

二、Promise概述

Promise是非同步程式設計的解決方案,比傳統的解決方案(回調和事件)更合理和更強大。它由社群最早提出和實現,ES6將其寫進了語言標準,統一了用法,原生提供了Promise物件。

Promise物件有且只有三種狀態:

#1、 pending:非同步作業未完成。

2、 resolved:非同步作業已完成。

3、 rejected:非同步操作失敗。

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

1、非同步操作從pending到resolved ;

2、非同步操作從pending到rejected;

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

恩,一目了然,Promise為建構函數,歐克,這樣透過它,我們就可以實例化自己的Promise物件了,並加以利用。

三、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对象的结果。

1、返回非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截图如下:

2、返回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(&#39;Monkey&#39;);
                },1000);
            });
            p.then(function(value){
                var p = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        var message = value + &#39; V Dorie&#39;
                        resolve(message);
                    },1000);
                });
                console.log(value);
                //返回一个Promise对象
                return p;
            }).then(function(value){
                console.log(value);
                return &#39;next is catch&#39;;
            }).catch(function(e){
                console.log(e);
            }).then(function(value){
                console.log(&#39;execute,but value is &#39; + value);
            });
        </script>
    </body>
</html>

执行上述代码,chrome截图如下:

好了,到这里,我们已经了解了最常用的Promise.prototype.then和Promise.prototype.catch这两个原型方法。另外,像Promise构造函数还有属于自身的方法,如all、rece、resolve、reject等,详情请点击这里(here)。

通过一路上对Promise的讲述,我们也有了一定的认识,其实Promise并没有想象中的那么难以理解嘛。懂得Promise概念后,其实我们自己也可以实现一个简易版的Promise。下面就一同尝试实现一个呗。

四、模拟Promise

假设:有三个异步操作方法f1,f2,f3,且f2依赖于f1,f3依赖于f2。如果,我们采用ES6中Promise链式调用的思想,我们可以将程序编写成这样:

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

那么,通过上面这一系列链式调用,怎样才能达到与ES6中Promise相似的功能呢?

初步想法:首先将上述链式调用的f2、f3保存到f1中,当f1中的异步执行完后,再调用执行f2,并将f1中的f3保存到f2中,最后,等f2中的异步执行完毕后,调用执行f3。详细构思图,如下:

从上图可知,由于f1、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方法还不够完美,因为它只能够满足于f1、f2、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,我们这一路走来,发现原来也不过如此呢。

 以上就是详细介绍JavaScript 中的 Promise的内容,更多相关内容请关注PHP中文网(www.php.cn)!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn