javascript 非同步編程2

來源:互聯網
上載者:User

好像有這麼一句名言——"每一個優雅的介面,背後都有一個齷齪的實現"。最明顯的例子,jQuery。之所以弄得這麼複雜,因為它本來就是那複雜。雖然有些實現相對簡明些,那是它們的相容程度去不了那個地步。當然,世上總有例外,比如mootools,但暴露到我們眼前的介面,又不知到底是那個父類的東西,結構清晰但不明撩。我之所以說這樣的話,因為非同步列隊真的很複雜,但我會儘可能讓API簡單易用。無new執行個體化,不區分執行個體與類方法,鏈式,等時髦的東西都用上。下面先奉上源碼:

;(function(){    var dom = this.dom = this.dom || {        mix : function(target, source ,override) {            var i, ride = (override === void 0) || override;            for (i in source) {                if (ride || !(i in target)) {                    target[i] = source[i];                }            }            return target;        }    }    //////////////////////////////////////////////////////////////////////    //=======================非同步列隊模組===================================    var Deferred = dom.Deferred = function (fn) {        return this instanceof Deferred ? this.init(fn) : new Deferred(fn)    }    var A_slice = Array.prototype.slice;    dom.mix(Deferred, {        get:function(obj){//確保this為Deferred執行個體            return  obj instanceof Deferred ? obj : new Deferred        },        ok : function (r) {//傳遞器            return r        },        ng : function (e) {//傳遞器            throw  e        }    });    Deferred.prototype = {        init:function(fn){//初始化,建立兩個列隊            this._firing = [];            this._fired = [];            if(typeof fn === "function")                return this.then(fn)            return this;        },        _add:function(okng,fn){            var obj = {                ok:Deferred.ok,                ng:Deferred.ng,                arr:[]            }            if(typeof fn === "function")                obj[okng] = fn;            this._firing.push(obj);            return this;        },        then:function(fn){//_add的封裝方法1,用於添加正向回調            return  Deferred.get(this)._add("ok",fn)        },        once:function(fn){//_add的封裝方法2,用於添加負向回調            return  Deferred.get(this)._add("ng",fn)        },        wait:function(timeout){            var self = Deferred.get(this);            self._firing.push(~~timeout)            return self        },        _fire:function(okng,args,result){            var type = "ok",            obj = this._firing.shift();            if(obj){                this._fired.push(obj);//把執行過的回呼函數包,從一個列隊倒入另一個列隊                var self = this;                if(typeof obj === "number"){//如果是延時操作                    var timeoutID = setTimeout(function(){                        self._fire(okng,self.before(args,result))                    },obj)                    this.onabort = function(){                        clearTimeout(timeoutID );                    }                }else if(obj.arr.length){//如果是並行操作                    var i = 0, d;                    while(d = obj.arr[i++]){                        d.fire(args)                    }                }else{//如果是串列操作                    try{//                        result = obj[okng].apply(this,args);                    }catch(e){                        type = "ng";                        result = e;                    }                    this._fire(type,this.before(args,result))                }            }else{//隊列執行完畢,還原                (this.after || Deferred.ok)(result);                this._firing = this._fired;                this._fired = [];            }            return this;        },        fire:function(){//執行正向列隊            return this._fire("ok",this.before(arguments));        },        error:function(){//執行負向列隊            return this._fire("ng",this.before(arguments));        },        abort:function(){//中止列隊            (this.onabort || Deferred.ok)();            return this;        },        //每次執行使用者回呼函數前都執行此函數,返回一個數組        before:function(args,result){           return result ? result instanceof Array ? result : [result] : A_slice.call(args)        },        //並行操作,並把所有的子線程的結果作為主線程的下一個操作的參數        paiallel : function (fns) {            var self = Deferred.get(this),            obj = {                ok:Deferred.ok,                ng:Deferred.ng,                arr:[]            },            count = 0,            values = {}            for(var key in fns){//參數可以是一個對象或數組                if(fns.hasOwnProperty(key)){                    (function(key,fn){                        if (typeof fn == "function")                            fn = Deferred(fn);                        fn.then(function(value){                            values[key] = value;                            if(--count  obj.end) {                        obj.last = true;                        obj.step = obj.end - i + 1;                    }                    obj.prev = result;                    result = fn.call(obj,i);                    Deferred.get(result).then(_loop).fire(i+step,obj);                }else{                    return result;                }            }            return (obj.begin 

Deferred提供的介面其實不算多,then once loop wait paialle就這五個,我們可以new一個執行個體出來,用它的執行個體方法,可以直接用類名加方法名,其實裡面還是new了一個執行個體。另外,還有兩個專門用於複寫的方法,before與after。before執行於每個回呼函數之前,after執行於所有回調之後,相當於complete了。既然是列隊,就有入隊操作與出隊操作,我不可能使用queue與dequeue這樣土的命名。queue換成兩個時間狀語,then與once,相當於jQuery的done、fail,或dojo的addCallback、addErrback。dequeue則用fire與error取替,jQuery1.5的beta版也曾經用過fire,不知為何換成resolve這樣難記的單詞。好了,我們先不管其他API,現在就試試身手吧。

      var log = function (s) {        window.console && window.console.log(s);      }      dom.Deferred(function () {        log(1);//1      })      .then(function () {        log(2);//2      })      .then(function () {        log(3);//3      })      .fire();//如果不使用非同步列隊,實現這種效果,就需要用套嵌函數/*      var fun = function(fn){        fn()      };      fun(function(){        log(1);        fun(function(){          log(2);          fun(function(){            log(3);          })        });      });*/

當然,現在是同步操作。注意,第一個回呼函數是作為構造器的參數傳入,可以節約了一個then^_^。

預設如果回呼函數存在傳回值,它會把它作為下一個回呼函數的參數傳入,如:

      dom.Deferred(function (a) {        a +=  10        log(a);//11        return a      })      .then(function (b) {        b += 12        log(b);//23        return b      })      .then(function (c) {        c += 130        log(c);//153      })      .fire(1);

我們可以重載before函數,讓它的結果不影響下一個回呼函數。在多投事件中,我們也可以在before中定義,如果返回false,就中斷隊列了。

我們再來看它如何處理異常。dom.Deferred的負向列隊與jQuery的是完全不同的,jQuery的只是正向列隊的一個複製,而在dom.Deferred中,負向列隊只是用於突發情況,是配角。

      dom.Deferred(function () {        log(1111111111)//11111111111      }).        then(function () {        throw "error!";//發生錯誤      }).        then(function (e) {//這個回呼函數不執行        log(e+"222222")        return 2222222      }).        once(function(e){//直到 遇上我們自訂的負向回調        log(e+'333333')//error!333333        return 333333333      }).        then(function (c) {//回到正向列隊中        log(c)//33333333      }).        fire()

</p><p><!doctype html><br /><html><br /> <head><br /> <title>非同步列隊 by 司徒正美 </title><br /> <script src="http://files.cnblogs.com/rubylouvre/dom_Deferred.js"><br /> </script><br /> <script><br /> var log = function(a){<br /> var div = document.createElement("div");<br /> div.innerHTML = a;<br /> document.body.appendChild(div);<br /> }<br /> window.onload = function(){<br /> dom.Deferred(function () {<br /> log(1111111111)//11111111111<br /> }).<br /> then(function () {<br /> throw "error!";//發生錯誤<br /> }).<br /> then(function (e) {//這個回呼函數不執行<br /> log(e+"222222")<br /> return 2222222<br /> }).<br /> once(function(e){//直到 遇上我們自訂的負向回調<br /> log(e+'333333')//error!333333<br /> return 333333333<br /> }).<br /> then(function (c) {//回到正向列隊中<br /> log(c)//33333333<br /> }).<br /> fire()</p><p> }<br /> </script><br /> </head><br /> <body></p><p> </body><br /></html><br />

運行代碼

上面幾個例子嚴格來說是同步執行,想實現非同步就要用到setTimeout。當然除了setTimeout,我們還有許多方案,img.onerror script.onreadystatechange script.onload xhr.onreadystatechange self.postMessage……但它們 都有一個缺點,就是不能指定回呼函數的執行時間。更何況setTimeout是沒有什麼相容問題,如img.onerrot就不能用於IE6-8,postMessage雖然很快,但只支援非常新的瀏覽器版本。我說過,非同步就是延時,延時就是等待,因此這方法叫做wait。

                dom.Deferred(function(){                    log(1)                }).wait(1000).then(function(){                    log(2)                }).wait(1000).then(function(){                    log(3)                }).wait(1000).then(function(){                    log(4)                }).fire()

</p><p><!doctype html><br /><html><br /> <head><br /> <title>非同步列隊 by 司徒正美 </title><br /> <script src="http://files.cnblogs.com/rubylouvre/dom_Deferred.js"><br /> </script><br /> <script><br /> var log = function(a){<br /> var div = document.createElement("div");<br /> div.innerHTML = a;<br /> document.body.appendChild(div);<br /> }<br /> window.onload = function(){<br /> dom.Deferred(function(){<br /> log(1)<br /> }).wait(1000).then(function(){<br /> log(2)<br /> }).wait(1000).then(function(){<br /> log(3)<br /> }).wait(1000).then(function(){<br /> log(4)<br /> }).fire()</p><p> }<br /> </script><br /> </head><br /> <body></p><p> </body><br /></html><br />

運行代碼

好了,我們看非同步列隊中最難的部分,並行操作。這相當於類比線程了,兩個不相干的東西各自做自己的事,互不干擾。當然在時間我們還是能看出先後順序來的。擔當這職責的方法為paiallel。

      dom.Deferred.paiallel([function(){          log("司徒正美")          return 11        },function(i){          log("上官莉綺")          return 12        },function(i){          log("朝沐金風")          return 13        }]).then(function(d){        log(d)      }).fire(10)

</p><p><!doctype html><br /><html><br /> <head><br /> <title>非同步列隊 by 司徒正美 </title><br /> <script src="http://files.cnblogs.com/rubylouvre/dom_Deferred.js"><br /> </script><br /> <script><br /> var log = function(a){<br /> var div = document.createElement("div");<br /> div.innerHTML = a;<br /> document.body.appendChild(div);<br /> }<br /> window.onload = function(){<br /> dom.Deferred.paiallel([function(){<br /> log("司徒正美")<br /> return 11<br /> },function(i){<br /> log("上官莉綺")<br /> return 12<br /> },function(i){<br /> log("朝沐金風")<br /> return 13<br /> }]).then(function(d){<br /> log(d)<br /> }).fire(10)</p><p> }<br /> </script><br /> </head><br /> <body></p><p> </body><br /></html><br />

運行代碼

不過,上面並沒有用到非同步,都是同步,這時,paiallel就相當於一個map操作。

        var d = dom.Deferred        d.paiallel([          d.wait(2000).then(function(a){            log("第1個子列隊");            return 123          }),          d.wait(1500).then(function(a){            log("第2個子列隊");            return 456          }),          d.then(function(a){            log("第3個子列隊")            return 789          })]).then(function(a){          log(a)        }).fire(3000);

</p><p><!doctype html><br /><html><br /> <head><br /> <title>非同步列隊 by 司徒正美 </title><br /> <script src="http://files.cnblogs.com/rubylouvre/dom_Deferred.js"><br /> </script><br /> <script><br /> var log = function(a){<br /> var div = document.createElement("div");<br /> div.innerHTML = a;<br /> document.body.appendChild(div);<br /> }<br /> window.onload = function(){<br /> var d = dom.Deferred<br /> d.paiallel([<br /> d.wait(2000).then(function(a){<br /> log("第1個子列隊");<br /> return 123<br /> }),<br /> d.wait(1500).then(function(a){<br /> log("第2個子列隊");<br /> return 456<br /> }),<br /> d.then(function(a){<br /> log("第3個子列隊")<br /> return 789<br /> })]).then(function(a){<br /> log(a)<br /> }).fire(3000);</p><p> }<br /> </script><br /> </head><br /> <body></p><p> </body><br /></html><br />

運行代碼

最後要介紹的是loop方法,它只要改變一下就能當作animate函數使用。

 d.loop(10, function(i){        log(i);        return d.wait(500)  });

</p><p><!doctype html><br /><html><br /> <head><br /> <title>非同步列隊 by 司徒正美 </title><br /> <script src="http://files.cnblogs.com/rubylouvre/dom_Deferred.js"><br /> </script><br /> <script><br /> var log = function(a){<br /> var div = document.createElement("div");<br /> div.innerHTML = a;<br /> document.body.appendChild(div);<br /> }<br /> window.onload = function(){<br /> var d = dom.Deferred<br /> d.loop(10, function(i){<br /> log(i);<br /> return d.wait(500)<br /> });<br /> }<br /> </script><br /> </head><br /> <body></p><p> </body><br /></html><br />

運行代碼

添加多個列隊,讓它們交錯進行,類比“多線程”效果。

      d.loop(10, function(i){        log("第一個列隊的第"+i+"個操作");        return d.wait(100)      });      d.loop(10, function(i){        log("第二個列隊的第"+i+"個操作");        return d.wait(100)      });      d.loop(10, function(i){        log("第三個列隊的第"+i+"個操作");        return d.wait(100)      });

</p><p><!doctype html><br /><html><br /> <head><br /> <title>非同步列隊 by 司徒正美 </title><br /> <script src="http://files.cnblogs.com/rubylouvre/dom_Deferred.js?1111111"><br /> </script><br /> <script><br /> var log = function(a){<br /> var div = document.createElement("div");<br /> div.innerHTML = a;<br /> document.body.appendChild(div);<br /> }<br /> window.onload = function(){<br /> var d = dom.Deferred;<br /> d.loop(10, function(i){<br /> log("第一個列隊的第"+i+"個操作");<br /> return d.wait(100)<br /> });<br /> d.loop(10, function(i){<br /> log("第二個列隊的第"+i+"個操作");<br /> return d.wait(100)<br /> });<br /> d.loop(10, function(i){<br /> log("第三個列隊的第"+i+"個操作");<br /> return d.wait(100)<br /> });<br /> }<br /> </script><br /> </head><br /> <body></p><p> </body><br /></html><br />

運行代碼

當然這些例子都很簡單,下次再結合ajax與動畫效果說說。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.