完全理解Javascript 中的 Promise(-------------------------------***---------------------------------)

ES6原生提供了 Promise 對象。javascript

究竟是何方妖怪呢?打出來看看:php

所謂 Promise,就是一個對象,用來傳遞異步操做的消息。它表明了某個將來纔會知道結果的事件(一般是一個異步操做),而且這個事件提供統一的 API,可供進一步處理。css

Promise 對象有如下兩個特色。html

(1)對象的狀態不受外界影響。Promise 對象表明一個異步操做,有三種狀態:Pending(進行中)、Resolved(已完成,又稱 Fulfilled)和 Rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。這也是 Promise 這個名字的由來,它的英語意思就是「承諾」,表示其餘手段沒法改變。java

(2)一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。Promise 對象的狀態改變,只有兩種可能:從 Pending 變爲 Resolved 和從 Pending 變爲 Rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果。就算改變已經發生了,你再對 Promise 對象添加回調函數,也會當即獲得這個結果。這與事件(Event)徹底不一樣,事件的特色是,若是你錯過了它,再去監聽,是得不到結果的。jquery

有了 Promise 對象,就能夠將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數。此外,Promise 對象提供統一的接口,使得控制異步操做更加容易。面試

Promise 也有一些缺點。首先,沒法取消 Promise,一旦新建它就會當即執行,沒法中途取消。其次,若是不設置回調函數,Promise 內部拋出的錯誤,不會反應到外部。第三,當處於 Pending 狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。ajax

廢話很少說,直接上demo:編程

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8" />
        <title>Promise 學習筆記</title>
        <script type="text/javascript">
            window.onload = function() {
                function pms1() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log('執行任務1');
                            resolve('執行任務1成功');
                        }, 2000);
                    });
                }

                function pms2() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log('執行任務2');
                            resolve('執行任務2成功');
                        }, 2000);
                    });
                }

                function pms3() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log('執行任務3');
                            resolve('執行任務3成功');
                        }, 2000);
                    });
                }
                pms1().then(function(data) {
                        console.log('第1個回調:' + data);
                        return pms2();
                    })
                    .then(function(data) {
                        console.log('第2個回調:' + data);
                        return pms3();
                    })
                    .then(function(data) {
                        console.log('第3個回調:' + data);
                        return '還沒完!該結束了吧!'
                    }).then(function(data) {
                        console.log(data);
                    });
            }
        </script>
    </head>

    <body>

    </body>

</html>

怎麼樣?是否是灰常簡單啊!api

demo2:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title></title>
        <script type="text/javascript">
            window.onload = function() {
                function pms1() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            var num = Math.ceil(Math.random() * 10); //生成1-10的隨機數
                            if(num <= 5) {
                                resolve(num);
                            } else {
                                reject('數字太大了吧!');
                            }
                        }, 2000);
                    });
                }
                setInterval(function() {
                    pms1().then(function(data) {    //小於等於5的
                        console.log(data);
                    }, function(data) {     //大於的
                        console.log(data);
                    })
                }, 1000);
            }
        </script>
    </head>

    <body>
    </body>

</html>

 

Promise 構造函數接受一個函數做爲參數,該函數的兩個參數分別是 resolve 方法和 reject 方法。

若是異步操做成功,則用 resolve 方法將 Promise 對象的狀態,從「未完成」變爲「成功」(即從 pending 變爲 resolved);

若是異步操做失敗,則用 reject 方法將 Promise 對象的狀態,從「未完成」變爲「失敗」(即從 pending 變爲 rejected)。

all的用法:

demo:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8" />
        <title>Promise 學習筆記</title>
        <script type="text/javascript">
            window.onload = function() {
                function pms1() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log('執行任務1');
                            resolve('執行任務1成功');
                        }, 2000);
                    });
                }

                function pms2() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log('執行任務2');
                            resolve('執行任務2成功');
                        }, 2000);
                    });
                }

                function pms3() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log('執行任務3');
                            resolve('執行任務3成功');
                        }, 2000);
                    });
                }
                Promise.all([pms1(), pms2(), pms3()]).then(function(data) {
                    console.log(data);
                    console.log({}.toString.call(data));
                })
            }
        </script>
    </head>

    <body>

    </body>

</html>

用Promise.all來執行,all接收一個數組參數,裏面的值最終都算返回Promise對象。這樣,三個異步操做的並行執行的,等到它們都執行完後纔會進到then裏面。那麼,三個異步操做返回的數據哪裏去了呢?都在then裏面呢,all會把全部異步操做的結果放進一個數組中傳給then,就是上面的results。

 

 

race的用法

all方法的效果其實是「誰跑的慢,以誰爲準執行回調」,那麼相對的就有另外一個方法「誰跑的快,以誰爲準執行回調」,這就是race方法,這個詞原本就是賽跑的意思。race的用法與all同樣,咱們把上面runAsync1的延時改成1秒來看一下:

 

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8" />
        <title>Promise 學習筆記</title>
        <script type="text/javascript">
            window.onload = function() {
                function pms1() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log('執行任務1');
                            resolve('執行任務1成功');
                        }, 1000);
                    });
                }

                function pms2() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log('執行任務2');
                            resolve('執行任務2成功');
                        }, 2000);
                    });
                }

                function pms3() {
                    return new Promise(function(resolve, reject) {
                        setTimeout(function() {
                            console.log('執行任務3');
                            resolve('執行任務3成功');
                        }, 3000);
                    });
                }
                Promise.race([pms1(), pms2(), pms3()]).then(function(data) {
                    console.log(data);   //注意上面的延時時間
                })
            }
        </script>
    </head>

    <body>

    </body>

</html>

看到沒: 只有第一個執行了回調!

 

在then裏面的回調開始執行時,runAsync2()和runAsync3()並無中止,仍舊再執行。因而再過1秒後,輸出了他們結束的標誌。
 
這個race有什麼用呢?使用場景仍是不少的,好比咱們能夠用race給某個異步請求設置超時時間,而且在超時後執行相應的操做。

 

 再來看看jquery裏面的$.Deferred:

        jquery用$.Deferred實現了Promise規範,$.Deferred是個什麼玩意呢?仍是老方法,打印出來看看,先有個直觀印象:
var def = $.Deferred();
console.log(def);
輸出以下:
$.Deferred()返回一個對象,咱們能夠稱之爲Deferred對象,上面掛着一些熟悉的方法如:done、fail、then等。jquery就是用這個Deferred對象來註冊異步操做的回調函數,修改並傳遞異步操做的狀態。
 
Deferred對象的基本用法以下,爲了避免與ajax混淆,咱們依舊舉setTimeout的例子:
<!doctype html>
<html lang="en">

    <head>
        <meta charset="UTF-8" />
        <title>Document</title>
        <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
        <script type="text/javascript">
            $(function() {
                function runAsync() {
                    var def = $.Deferred();
                    setTimeout(function() {
                        console.log('執行完成');
                        def.resolve('隨便什麼數據');
                    }, 2000);
                    return def;
                }
                runAsync().then(function(data) {
                    console.log(data)
                });
            })
        </script>
    </head>

    <body>

    </body>

</html>

在runAsync函數中,咱們首先定義了一個def對象,而後進行一個延時操做,在2秒後調用def.resolve(),最後把def做爲函數的返回。調用runAsync的時候將返回def對象,而後咱們就能夠.then來執行回調函數。

是否是感受和ES6的Promise很像呢?

區別在何處一看便知。因爲jquery的def對象自己就有resolve方法,因此咱們在建立def對象的時候並未像ES6這樣傳入了一個函數參數,是空的。在後面能夠直接def.resolve()這樣調用。
 
<!doctype html>
<html lang="en">

    <head>
        <meta charset="UTF-8" />
        <title>Document</title>
        <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
        <script type="text/javascript">
            $(function() {
                function runAsync() {
                    var def = $.Deferred();
                    setTimeout(function() {
                        console.log('執行完成');
                        def.resolve('隨便什麼數據');
                    }, 2000);
                    return def;
                }
                var pms=runAsync();
                pms.then(function(data) {
                    console.log(data)
                });
                pms.resolve('我穿越了!')
            })
        </script>
    </head>

    <body>

    </body>

</html>

這樣也有一個弊端,由於執行runAsync()能夠拿到def對象,而def對象上又有resolve方法,那麼豈不是能夠在外部就修改def的狀態了?好比我把上面的代碼修改以下:
現象會如何呢?並不會在2秒後輸出「執行完成」,而是直接輸出「我穿越了」。由於咱們在異步操做執行完成以前,沒等他本身resolve,就在外部給resolve了。這顯然是有風險的,好比你定義的一個異步操做並指定好回調函數,有可能被別人給提早結束掉,你的回調函數也就不能執行了。
 
怎麼辦?jquery提供了一個promise方法,就在def對象上,他能夠返回一個受限的Deferred對象,所謂受限就是沒有resolve、reject等方法,沒法從外部來改變他的狀態,用法以下:
<!doctype html>
<html lang="en">

    <head>
        <meta charset="UTF-8" />
        <title>Document</title>
        <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
        <script type="text/javascript">
            $(function() {
                function runAsync() {
                    var def = $.Deferred();
                    setTimeout(function() {
                        console.log('執行完成');
                        def.resolve('隨便什麼數據');
                    }, 2000);
                    return def.promise();
                }
                var pms=runAsync();
                pms.then(function(data) {
                    console.log(data)
                });
                //pms.resolve('我穿越了!');    //這一句會報錯jquery-3.1.1.min.js:2 Uncaught TypeError: pms.resolve is not a function
            })
        </script>
    </head>

    <body>

    </body>

</html>

 

then的鏈式調用

既然Deferred也是Promise規範的實現者,那麼其餘特性也必須是支持的。鏈式調用的用法以下:
<!doctype html>
<html lang="en">

    <head>
        <meta charset="UTF-8" />
        <title>Document</title>
        <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
        <script type="text/javascript">
            $(function() {
                function runAsync() {
                    var def = $.Deferred();
                    setTimeout(function() {
                        console.log('執行完成');
                        def.resolve('隨便什麼數據');
                    }, 2000);
                    return def.promise();
                }
                var pms = runAsync();

                pms.then(function(data) {
                        console.log('1:' + data);
                        return runAsync();
                    })
                    .then(function(data) {
                        console.log('2:' + data);
                        return runAsync();
                    })
                    .then(function(data) {
                        console.log('3:' + data);
                    });
                //pms.resolve('我穿越了!');    //這一句會報錯jquery-3.1.1.min.js:2 Uncaught TypeError: pms.resolve is not a function
            })
        </script>
    </head>

    <body>

    </body>

</html>

done與fail

咱們知道,Promise規範中,then方法接受兩個參數,分別是執行完成和執行失敗的回調,而jquery中進行了加強,還能夠接受第三個參數,就是在pending狀態時的回調,以下:
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
除此以外,jquery還增長了兩個語法糖方法,done和fail,分別用來指定執行完成和執行失敗的回調,也就是說這段代碼:
d.then(function(){
    console.log('執行完成');
}, function(){
    console.log('執行失敗');
});
與這段代碼是等價的:
d.done(function(){
    console.log('執行完成');
})
.fail(function(){
    console.log('執行失敗');
});

always的用法

jquery的Deferred對象上還有一個always方法,不論執行完成仍是執行失敗,always都會執行,有點相似ajax中的complete。不贅述了。
 

$.when的用法

jquery中,還有一個$.when方法來實現Promise,與ES6中的all方法功能同樣,並行執行異步操做,在全部的異步操做執行完後才執行回調函數。不過$.when並無定義在$.Deferred中,看名字就知道,$.when,它是一個單獨的方法。與ES6的all的參數稍有區別,它接受的並非數組,而是多個Deferred對象,以下:
$.when(runAsync(), runAsync2(), runAsync3())
.then(function(data1, data2, data3){
    console.log('所有執行完成');
    console.log(data1, data2, data3);
});
jquery中沒有像ES6中的race方法嗎?就是以跑的快的爲準的那個方法。對的,jquery中沒有。
以上就是jquery中Deferred對象的經常使用方法了,還有一些其餘的方法用的也很少,乾脆就不記它了。接下來該說說ajax了。
 

ajax與Deferred的關係

jquery的ajax返回一個受限的Deferred對象,還記得受限的Deferred對象吧,也就是沒有resolve方法和reject方法,不能從外部改變狀態。想一想也是,你發一個ajax請求,別人從其餘地方給你取消掉了,也是受不了的。
 
既然是Deferred對象,那麼咱們上面講到的全部特性,ajax也都是能夠用的。好比鏈式調用,連續發送多個請求:
req1 = function(){
    return $.ajax(/*...*/);
}
req2 = function(){
    return $.ajax(/*...*/);
}
req3 = function(){
    return $.ajax(/*...*/);
}

req1().then(req2).then(req3).done(function(){
    console.log('請求發送完畢');
});

 

明白了ajax返回對象的實質,那咱們用起來就駕輕就熟了。
 

success、error與complete

這三個方法或許是咱們用的最多的,使用起來是這樣的:
$.ajax(/*...*/)
.success(function(){/*...*/})
.error(function(){/*...*/})
.complete(function(){/*...*/})
分別表示ajax請求成功、失敗、結束的回調。這三個方法與Deferred又是什麼關係呢?其實就是語法糖,success對應done,error對應fail,complete對應always,就這樣,只是爲了與ajax的參數名字上保持一致而已,更方便你們記憶,看一眼源碼:
deferred.promise( jqXHR ).complete = completeDeferred.add;
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;
complete那一行那麼寫,是爲了減小重複代碼,其實就是把done和fail又調用一次,與always中的代碼同樣。deferred.promise( jqXHR )這句也能看出,ajax返回的是受限的Deferred對象。
 
jquery加了這麼些個語法糖,雖然上手門檻更低了,可是卻形成了必定程度的混淆。一些人雖然這麼寫了好久,卻一直不知道其中的原理,在面試的時候只能答出一些皮毛,這是很很差的。這也是我寫這篇文章的原因。
 
 
 
 
看一個promise.js庫:
/*!
 * Promise JavaScript Library v2.0.0
 */
;
(function(window) {
    var _promise = function(thens) {
        this.thens = thens || [];
        this.state = "";

        this._CONSTANT = {
            any: "any",
            number: "number",
            resolved: "resolved",
            rejected: "rejected",
            pending: "pending"
        };
    };

    _promise.prototype = {
        resolve: function() {
            if(this.state == this._CONSTANT.pending) {
                this.state = this._CONSTANT.resolved;
                return;
            }
            if(this.state !== "") return;
            if(this.promiseArr) {
                for(var i = 0, j = this.promiseArr.length; i < j; i++) {
                    this.promiseArr[i].resolveCount++;
                }
                if(this.promiseArr[0].action !== this._CONSTANT.any) {
                    if(this.resolveCount !== this.promiseArr.length) {
                        return;
                    }
                } else {
                    if(this.resolveCount > 1) {
                        return;
                    }
                }
            }
            this.state = this._CONSTANT.resolved;
            if(!this.thens) return;
            if(this.thens[0] && this.thens[0].finallyCB) this.thens[0].finallyCB.apply(null, arguments);
            var t, n;
            while(t = this.thens.shift()) {
                if(typeof t === this._CONSTANT.number) {
                    var self = this;
                    setTimeout(function() {
                        var prms = new _promise(self.thens);
                        prms.resolve();
                    }, t);
                    break;
                }
                var doneFn = t.done,
                    action = t.action;
                if(!doneFn) continue;
                if(doneFn instanceof Array) {
                    var arr = [];
                    for(var i = 0, j = doneFn.length; i < j; i++) {
                        var df = doneFn[i];
                        if(df instanceof _promise) {
                            df.thens = this.thens;
                            arr.push(df);
                        } else {
                            var m = df.apply(null, arguments);
                            if(m instanceof _promise) {
                                m.thens = this.thens;
                                arr.push(m);
                            }
                        }
                    }
                    var l = arr.length;
                    if(l === 0) {
                        continue;
                    } else {
                        for(var i = 0; i < l; i++) {
                            arr[i].promiseArr = arr;
                            arr[i].action = action;
                            arr[i].resolveCount = 0;
                        }
                        break;
                    }
                } else {
                    if(doneFn instanceof _promise) {
                        doneFn.thens = this.thens;
                        break;
                    } else {
                        n = doneFn.apply(null, arguments);
                        if(n instanceof _promise) {
                            n.thens = this.thens;
                            break;
                        }
                    }
                    continue;
                }

            }
        },

        reject: function() {
            if(this.state !== "") return;
            if(this.promiseArr && this.promiseArr[0].action === this._CONSTANT.any) {
                if(this.promiseArr[this.promiseArr.length - 1] !== this) {
                    return;
                }
            }
            this.state = this._CONSTANT.rejected;
            if(!this.thens) return;
            if(this.thens[0] && this.thens[0].finallyCB) this.thens[0].finallyCB.apply(null, arguments);
            var t, n;
            while(t = this.thens.shift()) {
                if(typeof t === this._CONSTANT.number) {
                    var self = this;
                    setTimeout(function() {
                        var prms = new _promise(self.thens);
                        prms.resolve();
                    }, t);
                    break;
                }
                if(t.fail) {
                    n = t.fail.apply(null, arguments);
                    if(n instanceof _promise) {
                        n.thens = this.thens;
                        break;
                    }
                    continue;
                }
                break;
            }
        },

        notify: function() {
            var t = this.thens[0];
            t.progress.apply(null, arguments);
        },

        then: function(done, fail, progress) {
            this.thens.push({
                done: done,
                fail: fail,
                progress: progress
            });
            return this;
        },

        any: function(done, fail, progress) {
            this.thens.push({
                done: done,
                fail: fail,
                progress: progress,
                action: this._CONSTANT.any
            });
            return this;
        },

        done: function(done) {
            if(this.thens.length === 0 || this.thens[this.thens.length - 1].done) {
                this.thens.push({
                    done: done
                });
            } else {
                this.thens[this.thens.length - 1].done = done;
            }
            return this;
        },

        fail: function(fail) {
            if(this.thens.length === 0 || this.thens[this.thens.length - 1].fail) {
                this.thens.push({
                    fail: fail
                });
            } else {
                this.thens[this.thens.length - 1].fail = fail;
            }
            return this;
        },

        progress: function(progress) {
            if(this.thens.length === 0 || this.thens[this.thens.length - 1].progress) {
                this.thens.push({
                    progress: progress
                });
            } else {
                this.thens[this.thens.length - 1].progress = progress;
            }
            return this;
        },

        ensure: function(finallyCB) {
            if(this.thens.length === 0 || this.thens[this.thens.length - 1].finallyCB) {

                this.thens.push({
                    finallyCB: finallyCB
                });
            } else {
                this.thens[this.thens.length - 1].finallyCB = finallyCB;
            }
            return this;
        },

        always: function(alwaysCB, progress) {
            this.thens.push({
                done: alwaysCB,
                fail: alwaysCB,
                progress: progress
            });
            return this;
        },

        wait: function(ms) {
            this.thens.push(~~ms);
            return this;
        }
    }

    var Promise = function(parameter) {
        var prms = new _promise();
        if(parameter) {
            if(arguments.length > 1) {
                prms.thens[0] = {};
                prms.thens[0].done = [];
                prms.thens[0].done.push.apply(prms.thens[0].done, arguments);
                setTimeout(function() {
                    prms.resolve();
                }, 1)
            } else {
                prms = parameter();
                if(prms instanceof _promise) return prms;
            }
        }
        return prms;
    };

    Promise.when = function() {
        var prms = new _promise();
        prms.thens[0] = {};
        prms.thens[0].done = [];
        prms.thens[0].done.push.apply(prms.thens[0].done, arguments);
        setTimeout(function() {
            prms.resolve();
        }, 1)
        return prms;
    };

    Promise.any = function() {
        var prms = new _promise();
        prms.thens[0] = {};
        prms.thens[0].action = prms._CONSTANT.any;
        prms.thens[0].done = [];
        prms.thens[0].done.push.apply(prms.thens[0].done, arguments);
        setTimeout(function() {
            prms.resolve();
        }, 1)
        return prms;
    };

    Promise.timeout = function(promise, ms) {
        setTimeout(function() {
            promise.reject();
        }, ms);
        return promise;
    }

    Promise.gtTime = function(promise, ms) {
        promise.state = promise._CONSTANT.pending;
        setTimeout(function() {
            if(promise.state == promise._CONSTANT.resolved) {
                promise.state = "";
                promise.resolve();
            }
            promise.state = "";
        }, ms);
        return promise;
    }

    if(typeof module === "object" && module && typeof module.exports === "object") {
        module.exports = Promise;
    } else {
        window.Promise = Promise;

        if(typeof define === "function" && define.amd) {
            define("promise", [], function() {
                return Promise;
            });
        }
    }
}(window));

promise.js提供了done和resolve方法,done負責註冊成功的回調函數,resolve負責觸發。

        function cb() {
            alert('success') } var prms = Promise() prms.done(cb) setTimeout(function() { prms.resolve() }, 3000)

 

在3秒以後,瀏覽器將alert  「success」。

 

固然你也能夠經過prms.resolve(「xxx」)傳遞參數給cb函數使用,如:

        function cb(num) {
            alert(num)
        }
        var prms = Promise() prms.done(cb) setTimeout(function() { prms.resolve(1) }, 3000)

在3秒以後,瀏覽器將alert  「1」。

fail/reject

fail函數負責註冊失敗的回調函數,reject負責觸發。如:

        function cb() {
            alert('fail') } var prms = Promise() prms.fail(cb) setTimeout(function () { prms.reject() }, 3000)

progress/notify

progress函數負責註冊處理中進度的回調函數,notify負責觸法。如:

        function cb() {
            alert('progress') } var prms = Promise() prms.progress(cb) setInterval(function() { prms.notify() }, 2000)

每隔兩秒瀏覽器會彈出一個progress。

chain

        function cb1() {
            alert('success') } function cb2() { alert('fail') } function cb3() { alert('progress') } var prms = Promise(); prms.done(cb1).fail(cb2).progress(cb3) setTimeout(function () { prms.resolve() //prms.reject() //prms.notify() }, 3000)

 

then

        function fn1() {
            alert('success') } function fn2() { alert('fail') } function fn3() { alert('progress') } var prms = Promise() prms.then(fn1, fn2, fn3) prms.resolve() prms.reject() prms.notify()

固然也支持prms.then().then().then()……….

當then的第一個參數爲一個數組的時候,要等全部task都完成:

f1().then([f2_1, f2_2]).then(f3)

如上面的代碼:

f1執行完後,同時執行f2_1和f2_2,當f2_1和f2_2所有都執行完成纔會執行f3。

any

f1().any([f2_1, f2_2]).then(f3)

f1執行完後,同時執行f2_1和f2_2,當f2_1和f2_2中的任意一個執行完成纔會執行f3。

always

        var prms = Promise()
        prms.always(function () { alert(2) }) setTimeout(function () { // prms.resolve() prms.reject() }, 3000)

always(fn)等同於then(fn,fn),也等同於done(fn).fail(fn)

wait

        function f10() {
            var promise = Promise(); setTimeout(function () { console.log(10); promise.resolve(); }, 4500) return promise; } function f11() { var promise = Promise(); setTimeout(function () { console.log(11); promise.resolve(); }, 1500) return promise; } f11().wait(5000).then(f10) //execute f11 then wait 5000ms then execute f10

 

ensure

ensure方法相似try…catch..finally中的finally,無論task成功失敗都會執行。

Promise.when

        Promise.when(f1(), f2()).then(f3).then(f4)
      
        function f1() {
            var promise = Promise(); setTimeout(function () { console.log(1); promise.resolve("from f1"); }, 1500) return promise; } function f2() { var promise = Promise(); setTimeout(function () { console.log(2); promise.resolve(); }, 5500) return promise; } function f3() { var promise = Promise(); setTimeout(function () { console.log(3); promise.resolve(); }, 1500) return promise; } function f4() { var promise = Promise(); setTimeout(function () { console.log(4); promise.resolve(); }, 1500) return promise; }

上面promise.when的等同簡略寫法也能夠是:Promise(f1(),f2()).then….

Promise.any

Promise.any的使用和when同樣,when的意義是等全部task都完成再執行後面的task,而any的意義是任何一個task完成就開始執行後面的task。

Promise.timeout

        Promise.timeout(f1(), 2000).then(f2, function () {
            alert("timeout"); }).wait(5000).then(f3); function f1() { var promise = Promise(); setTimeout(function () { console.log(1); promise.resolve("from f1"); }, 1500) return promise; } function f2() { var promise = Promise(); setTimeout(function () { console.log(2); promise.resolve(); }, 1500) return promise; } function f3() { var promise = Promise(); setTimeout(function () { console.log(3); promise.resolve(); }, 1500) return promise; }

with wind.js

    <script src="wind-all-0.7.3.js"></script> <script src="promise.js"></script> <script> Wind.Promise.create = function (fn) { var prms = Promise(); fn(prms.resolve, prms.reject); return prms; } var testAsync = eval(Wind.compile("promise", function () { for (var i = 0; i < 3; i++) { //loop 3 times var aa = $await(f1()); alert(aa); //alert 「from f1」 $await(f2().wait(3000)); //execute f2 then wait 3000ms $await(f3()); } })); testAsync(); function f1() { var promise = Promise(); setTimeout(function () { console.log(1); promise.resolve("from f1"); }, 2500) return promise; } function f2() { var promise = Promise(); setTimeout(function () { console.log(2); promise.resolve(); }, 1500) return promise; } function f3() { var promise = Promise(); setTimeout(function () { console.log(3); promise.resolve(); }, 1500) return promise; } </script>

That’s all.Have Fun!

 

 

 
 

用Promise組織程序

1、Promise基本用法

不少文章介紹Promise給的例子是這樣的:

new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open('POST', location.href, true); xhr.send(null); xhr.addEventListener('readystatechange', function(e){ if(xhr.readyState === 4) { if(xhr.status === 200) { resolve(xhr.responseText); } else { reject(xhr); } } }) }).then(function(txt){ console.log(); })

必定會有小朋友好奇,說尼瑪,這不是比回調還噁心?

這種寫法的確是能跑得起來啦……不過,按照Promise的設計初衷,咱們編程須要使用的概念並不是"Promise對象",而是promise函數,凡是以Promise做爲返回值的函數,稱爲promise函數(我暫且取了這個名字)。因此應該是這樣的:

function doSth() { return new Promise(function(resolve, reject) { //作點什麼異步的事情 //結束的時候調用 resolve,好比: setTimeout(function(){ resolve(); //這裏纔是真的返回 },1000) }) }

若是你不喜歡這樣的寫法,還可使用defer風格的promise

function doSth2() { var defer = Promise.defer(); //作點什麼異步的事情 //結束的時候調用 defer.resolve,好比: setTimeout(function(){ defer.resolve(); //這裏纔是真的返回 },1000) return defer.promise; }

總之兩種是沒什麼區別啦。

而後你就能夠這麼幹:

doSth().then(doSth2).then(doSth);

這樣看起來就順眼多了吧。

其實說簡單點,promise最大的意義在於把嵌套的回調變成了鏈式調用(詳見第三節,順序執行),好比如下

//回調風格 loadImage(img1,function(){ loadImage(img2,function(){ loadImage(img3,function(){ }); }); }); //promise風格 Promise.resolve().then(function(){ return loadImage(img1); }).then(function(){ return loadImage(img2); }).then(function(){ return loadImage(img3); });

後者嵌套關係更少,在多數人眼中會更易於維護一些。

2、Promise風格的API

在去完cssconf回杭州的火車上,我順手把一些常見的JS和API寫成了promise方式:

function get(uri){ return http(uri, 'GET', null); } function post(uri,data){ if(typeof data === 'object' && !(data instanceof String || (FormData && data instanceof FormData))) { var params = []; for(var p in data) { if(data[p] instanceof Array) { for(var i = 0; i < data[p].length; i++) { params.push(encodeURIComponent(p) + '[]=' + encodeURIComponent(data[p][i])); } } else { params.push(encodeURIComponent(p) + '=' + encodeURIComponent(data[p])); } } data = params.join('&'); } return http(uri, 'POST', data || null, { "Content-type":"application/x-www-form-urlencoded" }); } function http(uri,method,data,headers){ return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open(method,uri,true); if(headers) { for(var p in headers) { xhr.setRequestHeader(p, headers[p]); } } xhr.addEventListener('readystatechange',function(e){ if(xhr.readyState === 4) { if(String(xhr.status).match(/^2\d\d$/)) { resolve(xhr.responseText); } else { reject(xhr); } } }); xhr.send(data); }) } function wait(duration){ return new Promise(function(resolve, reject) { setTimeout(resolve,duration); }) } function waitFor(element,event,useCapture){ return new Promise(function(resolve, reject) { element.addEventListener(event,function listener(event){ resolve(event) this.removeEventListener(event, listener, useCapture); },useCapture) }) } function loadImage(src) { return new Promise(function(resolve, reject) { var image = new Image; image.addEventListener('load',function listener() { resolve(image); this.removeEventListener('load', listener, useCapture); }); image.src = src; image.addEventListener('error',reject);})}function runScript(src){returnnewPromise(function(resolve, reject){var script = document.createElement('script'); script.src = src; script.addEventListener('load',resolve); script.addEventListener('error',reject);(document.getElementsByTagName('head')[0]|| document.body || document.documentElement).appendChild(script);})}function domReady(){returnnewPromise(function(resolve, reject){if(document.readyState ==='complete'){ resolve();}else{ document.addEventListener('DOMContentLoaded',resolve);}})}

看到了嗎,Promise風格API跟回調風格的API不一樣,它的參數跟同步的API是一致的,可是它的返回值是個Promise對象,要想獲得真正的結果,須要在then的回調裏面拿到。

值得一提的是,下面這樣的寫法:

waitFor(document.documentElement,'click').then(function(){ console.log('document clicked!') })

這樣的事件響應思路上是比較新穎的,不一樣於事件機制,它的事件處理代碼僅會執行一次。

經過這些函數的組合,咱們能夠更優雅地組織異步代碼,請繼續往下看。

3、使用Promise組織異步代碼

函數調用/並行

Promise.all跟then的配合,能夠視爲調用部分參數爲Promise提供的函數。譬如,咱們如今有一個接受三個參數的函數

function print(a, b, c) { console.log(a + b + c); }

如今咱們調用print函數,其中a和b是須要異步獲取的:

var c = 10; print(geta(), getb(), 10); //這是同步的寫法 Promise.all([geta(), getb(), 10]).then(print); //這是 primise 的異步寫法

競爭

若是說Primise.all是promise對象之間的「與」關係,那麼Promise.race就是promise對象之間的「或」關係。

好比,我要實現「點擊按鈕或者5秒鐘以後執行」

var btn = document.getElementsByTagName('button'); Promise.race(wait(5000), waitFor(btn, click)).then(function(){ console.log('run!') })

異常處理

異常處理一直是回調的難題,而promise提供了很是方便的catch方法:

在一次promise調用中,任何的環節發生reject,均可以在最終的catch中捕獲到

Promise.resolve().then(function(){ return loadImage(img1); }).then(function(){ return loadImage(img2); }).then(function(){ return loadImage(img3); }).catch(function(err){ //錯誤處理 })

這很是相似於JS的try catch功能。

複雜流程

接下來,咱們來看比較複雜的狀況。

promise有一種很是重要的特性:then的參數,理論上應該是一個promise函數,而若是你傳遞的是普通函數,那麼默認會把它當作已經resolve了的promise函數。

這樣的特性讓咱們很是容易把promise風格的函數跟已有代碼結合起來。

爲了方便傳參數,咱們編寫一個currying函數,這是函數式編程裏面的基本特性,在這裏跟promise很是搭,因此就實現一下:

function currying(){ var f = arguments[0]; var args = Array.prototype.slice.call(arguments,1); return function(){ args.push.apply(args,arguments); return f.apply(this,args); } }

currying會給某個函數"固化"幾個參數,而且返回接受剩餘參數的函數。好比以前的函數,能夠這麼玩:

var print2 = currying(print,11); print2(2, 3); //獲得 11 + 2 + 3 的結果,16 var wait1s = currying(wait,1000); wait1s().then(function(){ console.log('after 1s!'); })

有了currying,咱們就能夠愉快地來玩鏈式調用了,好比如下代碼:

Promise.race([ domReady().then(currying(wait,5000)), waitFor(btn, click)]) .then(currying(runScript,'a.js')) .then(function(){ console.log('loaded'); return Promise.resolve(); });

表示「domReady發生5秒或者點擊按鈕後,加載腳本並執行,完畢後打印loaded」。

4、總結

promise做爲一個新的API,是幾乎徹底可polyfill的,這在JS發展中這是不多見的狀況,它的API自己沒有什麼特別的功能,可是它背後表明的編程思路是頗有價值的。

 
 

  

 
 
 
 
 

一.deferred對象簡介

deferred對象是jquery回調函數的解決方案,解決了如何處理耗時操做的問題,對耗時操做提供了更好的控制,以及統一的編程接口。

二.語法

1.與ajax進行寫法比較:

複製代碼
 1      //ajax傳統寫法
 2      //$.ajax()接受一個對象參數,這個對象包含兩個方法:
 3      //success方法指定操做成功後的回調函數,error方法指定操做失敗後的回調函數。
 4     $.ajax({
 5     url: "test.html",
 6     success: function(){
 7       alert("哈哈,成功了!");
 8     },
 9     error:function(){
10       alert("出錯啦!");
11     }
12   });
13 
14       //deferred對象寫法
15       $.ajax("test.html")
16        .done(function(){alert("哈哈,成功了!");})
17        .fail(function(){alert("出錯啦!");});
18        //能夠看到,done()至關於success方法,fail()至關於error方法。採用鏈式寫法之後,代碼的可讀性大大提升。
複製代碼

2.指定同一操做的多個回調函數

1 //deferred對象的一大好處,就是它容許你自由添加多個回調函數。
2 //回調函數能夠添加任意多個,它們按照添加順序執行。
3     $.ajax("test.html")
4   .done(function(){ alert("哈哈,成功了!");} )
5   .fail(function(){ alert("出錯啦!"); } )
6   .done(function(){ alert("第二個回調函數!");} );    

3.爲多個操做指定回調函數

複製代碼
1 //deferred對象的另外一大好處,就是它容許你爲多個事件指定一個回調函數
2     $.when($.ajax("test1.html"), $.ajax("test2.html"))
3   .done(function(){ alert("哈哈,成功了!"); })
4   .fail(function(){ alert("出錯啦!"); });
5 //這段代碼的意思是,先執行兩個操做$.ajax("test1.html")和$.ajax("test2.html")
6 //若是都成功了,就運行done()指定的回調函數;若是有一個失敗或都失敗了,就執行fail()指定的回調函數。
7 //when()參數的關係相似於邏輯運算「與」
複製代碼

4.deferred.resolve()方法和deferred.reject()方法

jQuery規定,deferred對象有三種執行狀態----未完成,已完成和已失敗。若是執行狀態是"已完成"(resolved),deferred對象馬上調用done()方法指定的回調函數;若是執行狀態是"已失敗",調用fail()方法指定的回調函數;若是執行狀態是"未完成",則繼續等待,或者調用progress()方法(progress():當Deferred(延遲)對象生成進度通知時,調用添加處理程序。參數能夠是一個單一的函數或函數數組。)指定的回調函數。

deferred.resolve()方法和deferred.reject()方法就是爲了改變deferred對象的狀態,從而決定執行哪一個回調函數。

複製代碼
 1     var dtd = $.Deferred(); // 新建一個Deferred對象
 2   var wait = function(dtd){
 3    var tasks = function(){
 4       alert("執行完畢!");
 5       dtd.reject(); // 改變Deferred對象的執行狀態
 6     };
 7     setTimeout(tasks,5000);
 8     return dtd;
 9   };
10   $.when(wait(dtd))
11   .done(function(){ alert("哈哈,成功了!"); })
12   .fail(function(){ alert("出錯啦!"); });
複製代碼

5.deferred.promise()方法

第4點代碼中的dtd對象是全局對象,因此它的執行狀態能夠從外部改變。

複製代碼
 1       var dtd = $.Deferred(); // 新建一個Deferred對象
 2   var wait = function(dtd){
 3     var tasks = function(){
 4       alert("執行完畢!");
 5       dtd.resolve(); // 改變Deferred對象的執行狀態
 6     };
 7     setTimeout(tasks,5000);
 8     return dtd;
 9   };
10   $.when(wait(dtd))
11   .done(function(){ alert("哈哈,成功了!"); })
12   .fail(function(){ alert("出錯啦!"); });
13   dtd.resolve();
14 //在代碼的尾部加了一行dtd.resolve(),這就改變了dtd對象的執行狀態,所以致使done()方法馬上執行,跳出"哈哈,成功了!"的提示框,等5秒以後再跳出"執行完畢!"的提示框
複製代碼

爲了不這種狀況,jQuery提供了deferred.promise()方法。它的做用是,在原來的deferred對象上返回另外一個deferred對象,後者只開放與改變執行狀態無關的方法(好比done()方法和fail()方法),屏蔽與改變執行狀態有關的方法(好比resolve()方法和reject()方法),從而使得執行狀態不能被改變。

複製代碼
 1    var dtd = $.Deferred(); // 新建一個Deferred對象
 2   var wait = function(dtd){
 3   var tasks = function(){
 4       alert("執行完畢!");
 5       dtd.resolve(); // 改變Deferred對象的執行狀態
 6   };
 7 
 8     setTimeout(tasks,5000);
 9     return dtd.promise(); // 返回promise對象
10   };
11   var d = wait(dtd); // 新建一個d對象,改成對這個對象進行操做
12   $.when(d)
13   .done(function(){ alert("哈哈,成功了!"); })
14   .fail(function(){ alert("出錯啦!"); });
15   d.resolve(); // 此時,這個語句是無效的
16 //在上面的這段代碼中,wait()函數返回的是promise對象。而後,咱們把回調函數綁定在這個對象上面,而不是原來的deferred對象上面。這樣的好處是,沒法改變這個對象的執行狀態,要想改變執行狀態,只能操做原來的deferred對象。
17 
18 //或者能夠將dtd對象變成wait()函數的內部對象:
19        var wait = function(dtd){
20     var dtd = $.Deferred(); //在函數內部,新建一個Deferred對象
21     var tasks = function(){
22       alert("執行完畢!");
23       dtd.resolve(); // 改變Deferred對象的執行狀態
24     };
25 
26     setTimeout(tasks,5000);
27     return dtd.promise(); // 返回promise對象
28   };
29   $.when(wait())
30   .done(function(){ alert("哈哈,成功了!"); })
31   .fail(function(){ alert("出錯啦!"); });
複製代碼

6.普通操做的回調函數接口

(1)deferred對象的最大優勢,就是它把這一套回調函數接口,從ajax操做擴展到了全部操做。也就是說,任何一個操做----無論是ajax操做仍是本地操做,也無論是異步操做仍是同步操做----均可以使用deferred對象的各類方法,指定回調函數。

 使用$.when():參數只能是deferred對象。

假定有一個很耗時的操做wait,爲它指定一個回調函數。

複製代碼
 1    var dtd = $.Deferred(); // 新建一個deferred對象
 2   var wait = function(dtd){
 3     var tasks = function(){
 4       alert("執行完畢!");
 5       dtd.resolve(); // 改變deferred對象的執行狀態
 6     };
 7     setTimeout(tasks,5000);
 8     return dtd;
 9   };
10 //wait()函數返回的是deferred對象,能夠加上鍊式操做。
11   $.when(wait(dtd))
12   .done(function(){ alert("哈哈,成功了!"); })
13   .fail(function(){ alert("出錯啦!"); });
14 //wait()函數運行完,就會自動運行done()方法指定的回調函數。
複製代碼

(2)防止執行狀態被外部改變的方法:使用deferred對象的建構函數$.Deferred(),

wait函數仍是保持不變,咱們直接把它傳入$.Deferred():

$.Deferred()能夠接受一個函數名做爲參數,$.Deferred()所生成的deferred對象將做爲這個函數的默認參數。

1     $.Deferred(wait)
2   .done(function(){ alert("哈哈,成功了!"); })
3   .fail(function(){ alert("出錯啦!"); });

(3)能夠直接在wait對象上部署deferred接口:

複製代碼
 1    var dtd = $.Deferred(); // 生成Deferred對象
 2   var wait = function(dtd){
 3    var tasks = function(){
 4       alert("執行完畢!");
 5       dtd.resolve(); // 改變Deferred對象的執行狀態
 6     };
 7     setTimeout(tasks,5000);
 8   };
 9   dtd.promise(wait);//在wait對象上部署Deferred接口。正是由於有了這一行,後面才能直接在wait上面調用done()和fail()。
10   wait.done(function(){ alert("哈哈,成功了!"); })
11   .fail(function(){ alert("出錯啦!"); });
12   wait(dtd);
複製代碼

7.總結

      (1) $.Deferred() 生成一個deferred對象。

  (2) deferred.done() 指定操做成功時的回調函數

  (3) deferred.fail() 指定操做失敗時的回調函數

  (4) deferred.promise() 沒有參數時,返回一個新的deferred對象,該對象的運行狀態沒法被改變;接受參數時,做用爲在參數對象上部署deferred接口。

  (5) deferred.resolve() 手動改變deferred對象的運行狀態爲"已完成",從而當即觸發done()方法。

  (6)deferred.reject() 這個方法與deferred.resolve()正好相反,調用後將deferred對象的運行狀態變爲"已失敗",從而當即觸發fail()方法。

  (7) $.when() 爲多個操做指定回調函數。

  (8)deferred.then():有時爲了省事,能夠把done()和fail()合在一塊兒寫,這就是then()方法。

1     $.when($.ajax( "/main.php" ))
2   .then(successFunc, failureFunc );

若是then()有兩個參數,那麼第一個參數是done()方法的回調函數,第二個參數是fail()方法的回調方法。若是then()只有一個參數,那麼等同於done()。

  (9)deferred.always():這個方法也是用來指定回調函數的,它的做用是,無論調用的是deferred.resolve()仍是deferred.reject(),最後老是執行。

1     $.ajax( "test.html" )
2   .always( function() { alert("已執行!");} );

 

文檔參考:

相關文章
相關標籤/搜索