好像有這麼一句名言——"每一個優雅的介面,背後都有一個齷齪的實現"。最明顯的例子,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與動畫效果說說。