JavaScript當前有衆多實現異步編程的方式,最爲耀眼的就是ECMAScript 6規範中的Promise對象,它來自於CommonJS小組的努力:Promise/A+規範。 javascript
研究javascript的異步編程,jsDeferred也是有必要探索的:由於Promise/A+規範的制定基本上是奠基在jsDeferred上,它是javascript異步編程中里程碑式的做品。jsDeferred自身的實現也是很是有意思的。html
本文將探討項目jsDeferred的模型,帶咱們感覺一個不同的異步編程體驗和實現。java
本文內容以下:git
- jsDeferred和Promise/A+
- jsDeferred的工做模型
- jsDeferred API
- 參考和引用
在上一篇文章《JavaScript異步編程(1)- ECMAScript 6的Promise對象》中,咱們討論了ECMAScript 6的Promise對象,這一篇咱們來看javascript異步編程的先驅者——jsDeferred。github
jsDeferred是日本javascript高手geek cho45受MochiKit.Async.Deferred模塊啓發在2007年開發(07年就在玩這個了...)的一個異步執行類庫。咱們將jsDeferred的原型和Promise/A+規範(譯文戳這裏)進行對比(來自^_^肥仔John的《JS魔法堂:jsDeferred源碼剖析》):ajax
- Promise是基於狀態的
- 狀態標識:pending(初始狀態)、fulfilled(成功狀態)和rejected(失敗狀態)。
- 狀態爲單方向移動「pending->fulfilled","pending->rejected"。
- 因爲存在狀態標識,因此支持晚事件處理的晚綁定。
- jsDeferred是基於事件的,並無狀態標識
- 實例的成功/失敗事件是基於事件觸發而被調用
- 由於沒有狀態標識,因此能夠屢次觸發成功/失敗事件
- 不支持晚綁定
下面一張圖粗略演示了jsDeferred的工做模型。編程
下面涉及到jsDeferred的源碼,對於第一次接觸的童鞋請直接拉到API一節(下一節),讀完了API再來看這裏。segmentfault
jsDeferred第一次調用next有着不一樣的處理,jsDeferred在第一次調用next()的時候,會當即異步執行這個回調函數——而這個掛起異步,則視當前的環境(如瀏覽器最佳環境)選擇最優的異步掛起方案,例如現代瀏覽器下會經過建立Image對象的方式來進行異步掛起,摘錄源碼以下:api
Deferred.next_faster_way_Image = ((typeof window === 'object') && (typeof (Image) != "undefined") && !window.opera && document.addEventListener) && function (fun) { // Modern Browsers var d = new Deferred(); var img = new Image(); var handler = function () { d.canceller(); d.call(); }; //進行異步掛起 img.addEventListener("load", handler, false); img.addEventListener("error", handler, false); d.canceller = function () { img.removeEventListener("load", handler, false); img.removeEventListener("error", handler, false); }; img.src = "data:image/png," + Math.random(); if (fun) d.callback.ok = fun; return d; };
Deferred對象的靜態方法 - Deferred.next()源碼:數組
Deferred.next = Deferred.next_faster_way_readystatechange ||//IE下使用onreadystatechange() Deferred.next_faster_way_Image ||//現代瀏覽器下使用Image對象onload/onerror事件 Deferred.next_tick ||//Node下使用process.nextTick() Deferred.next_default;//默認使用setTimeout
咱們務必要理清Deferred.next()和Deferred.prototype.next(),這是兩種不一樣的東西:
摘錄源碼以下:
Deferred.prototype = { callback: {}, next: function (fun) {//壓入一個函數並返回新的Deferred對象 return this._post("ok", fun) }, call: function (val) {//觸發當前Deferred成功的事件 return this._fire("ok", val) }, _post: function (okng, fun) {//next()底層 this._next = new Deferred(); this._next.callback[okng] = fun; return this._next; }, _fire: function (okng, value) {//call()底層 var next = "ok"; try { //調用deferred對象相應的事件處理函數 value = this.callback[okng].call(this, value); } catch (e) { //拋出異常則進入fail() next = "ng"; value = e; if (Deferred.onerror) Deferred.onerror(e); } if (Deferred.isDeferred(value)) { //在這裏,和_post()呼應,調用Deferred鏈的下一個Deferred對象 value._next = this._next; } else { if (this._next) this._next._fire(next, value); } return this; } }
再一次強調,務必搞清楚Deferred.next()和Deferred.prototype.next()。
當我第一次知道jsDeferred API有一坨的時候,其實我是,是拒絕的。我跟你講,我拒絕,由於其實我以爲這根本要不了一坨,但正妹跟我講,jsDeferred內部會加特技,是假的一坨,是表面看起來一坨。加了特技以後,jsDeferred duang~duang~duang~,很酷,很炫,很酷炫。
jsDeferred的API衆多,由於jsDeferred把全部的異步問題都劃分到了最小的粒子,這些API相互進行組合則能夠完成逆天的異步能力,在後續的API示例中能夠看到jsDeferred API組合從而完成強大的異步編程。咱們在閱讀jsDeferred的API的時候應該時刻思考若是使用ES6的Promise對象又該如何去處理,閱讀應該是大腦的盛宴。
貌似沒有看到過jsDeferred的詳細的中文API文檔(原API文檔),就這裏順便整理一份簡單的出來(雖然它的API已經足夠通俗易懂了)。值得一提的是官網的API引導例子很是的生動和實用:
Deferred()/new Deferred ()
構造函數(constructor),建立一個Deferred對象。
var defer = Deferred();//或new Deferred() //建立一個Deferred對象 defer.next(function () { console.log('ok'); }).error(function (text) { console.log(text);//=> linkFly }).fail('linkFly');
實例方法
Deferred.prototype.next和Deferred.prototype.call
Deferred.prototype.next()構建一個全新的Deferred對象,併爲它綁定成功事件處理函數,在沒有調用Deferred.prototype.call()以前這個事件處理函數並不會執行。
var deferred = Deferred(); deferred.next(function (value) { console.log(value); // => linkFly }).call('linkFly');
Deferred.prototype.error和Deferred.prototype.fail
Deferred.prototype.error()構建一個全新的Deferred對象,併爲它綁定失敗事件處理函數,在沒有調用Deferred.prototype.fail()以前這個事件處理函數並不會執行。
var deferred = Deferred(); deferred.error(function () { console.log('error');// => error }).fail();
靜態方法。Deferred全部的靜態方法,均可以使用Deferred.方法名()__的方式調用。__
Deferred.define(obj, list)
暴露靜態方法到obj上,無參的狀況下obj是全局對象:侵入性極強,但使用方便。list是一組方法,這組方法會同時註冊到obj上。
Deferred.define();//無參,侵入式,默認全局對象,瀏覽器環境爲window next(function () { console.log('ok'); });//靜態方法入next被註冊到了window下 var defer = {}; Deferred.define(defer);//非侵入式,Deferred的靜態方法註冊到了defer對象下 defer.next(function () { console.log('ok'); });
Deferred.isDeferred(obj)
判斷對象obj是不是jsDeferred對象的實例(Deferred對象)。
Deferred.define(); console.log(Deferred.isDeferred({}));//=> false console.log(Deferred.isDeferred(wait(2)));//=> true
Deferred.call(fn[,args]*)
建立一個Deferred實例,而且觸發其成功事件。fn是成功後要執行的函數,後續的參數表示傳遞給fn的參數。
call(function (text) { console.log(text);//=> linkFly }, 'linkFly'); console.log('hello,world!');// => 先輸出
Deferred.next(fn)
建立一個Deferred實例,而且觸發其成功事件。fn是成功後要執行的函數,它等同於只有一個參數的call,即:Deferred.call(fn)
Deferred.define(); next(function () { console.log('ok'); }); console.log('hello,world!');// => 先輸出 //上面的代碼等同於下面的代碼 call(function () { console.log('ok'); }); console.log('hello,world!');// => 先輸出
Deferred.wait(time)
建立一個Deferred實例,並等待time(秒)後觸發其成功事件,下面的代碼首先彈出"Hello,",2秒後彈出"World!"。
next(function () { alert('Hello,'); return wait(2);//延遲2s後執行 }). next(function (r) { alert('World!'); }); console.log('hello,world!');// => 先輸出
Deferred.loop(n, fun)
循環執行n次fun,並將最後一次執行fun()的返回值做爲Deferred實例成功事件處理函數的參數,一樣loop中循環執行的fun()也是異步的。
loop(3, function () { console.log(count); return count++; }).next(function (value) { console.info(value);// => 2 }); //上面的代碼也是異步的(無阻塞的) console.info('linkFly');
Deferred.parallel(dl[ ,fn]*)
把參數中非Deferred對象均轉換爲Deferred對象(經過Deferred.next()),而後並行觸發dl中的Deferred實例的成功事件。
當全部Deferred對象均調用了成功事件處理函數後,返回的Deferred實例則觸發成功事件,而且全部返回值將被封裝爲數組做爲Deferred實例的成功事件處理函數的入參。
parallel()強悍之處在於它的並歸處理,它能夠將參數中屢次的異步最終並歸到一塊兒,這一點在JavaScript ajax嵌套中尤其重要:例如同時發送2條ajax請求,最終parallel()會並歸這2條ajax返回的結果。
parallel()進行了3次重載:
下面一張圖演示了Deferred.parallel的工做模型,它能夠理解爲合併了3次ajax請求。
Deferred.define(); parallel(function () { //等待2秒後執行 return wait(2).next(function () { return 'hello,'; }); }, function () { return wait(1).next(function () { return 'world!' }); }).next(function (values) { console.log(values);// => ["hello,", "world!"] });
當parallel傳遞的參數是一個對象的時候,返回值則是一個對象:
parallel({ foo: wait(1).next(function () { return 1; }), bar: wait(2).next(function () { return 2; }) }).next(function (values) { console.log(values);// => Object { foo=1, bar=2 } });
和jQuery.when()一模一樣。
Deferred.earlier(dl[ ,fn]*)
當參數中某一個Deferred對象調用了成功處理函數,則終止參數中其餘Deferred對象的觸發的成功事件,返回的Deferred實例則觸發成功事件,而且那個觸發成功事件的函數返回值將做爲Deferred實例的成功事件處理函數的入參。
注意:Deferred.earlier()並不會經過Deferred.define(obj)暴露給obj,它只能經過Deferred.earlier()調用。
Deferred.earlier()內部的實現和Deferred.parallel()大同小異,但值得注意的是參數,它接受的是Deferred,而不是parallel()的Function:
Deferred.define(); Deferred.earlier( wait(2).next(function () { return 'cnblog'; }), wait(1).next(function () { return 'linkFly' })//1s後執行成功 ).next(function (values) { console.log(values);// 1s後 => [undefined, "linkFly"] });
Deferred.repeat(n, fun)
循環執行fun方法n次,若fun的執行事件超過20毫秒則先將UI線程的控制權交出,等一下子再執行下一輪的循環。
本身跑了一下,跑出問題來了...duang...求道友指點下迷津
Deferred.define(); repeat(10, function (i) { if (i === 6) { var starTime = new Date(); while (new Date().getTime() - starTime < 50) console.info(new Date().getTime() - starTime);//到6以後時候不該該再執行了,由於這個函數的執行超過了20ms } console.log(i); //=> 0,1,2,3,4,5,6,7,8,9 });
Deferred.chain(args)
chain()方法的參數比較獨特,能夠接受多個參數,參數類型能夠是:Function,Object,Array。
chain()方法比較難懂,它是將全部的參數構造出一條Deferred方法鏈。
例如Function類型的參數:
Deferred.define(); chain( function () { console.log('start'); }, function () { console.log('linkFly'); } ); //等同於 next(function () { console.log('start'); }).next(function () { console.log('linkFly'); });
它經過函數名來判斷函數:
chain( //函數名!=error,則默認爲next function () { throw Error('error'); }, //函數名爲error function error(e) { console.log(e.message); } ); //等同於 next(function () { throw Error('error'); }).error(function (e) { console.log(e.message); });
也支持Deferred.parallel()的方式:
chain( [ function () { return wait(1); }, function () { return wait(2); } ] ).next(function () { console.log('ok'); }); //等同於 Deferred.parallel([ function () { return wait(1); }, function () { return wait(2); } ]).next(function () { console.log('ok'); });
固然能夠組合參數:
chain( function () { throw Error('error'); }, //函數名爲error function error(e) { console.log(e.message); }, //組合Deferred.parallel()的方式 [ function () { return wait(1); }, function () { return wait(2); } ] ).next(function () { console.log('ok'); }); //等同於 next(function () { throw Error('error'); }).error(function (e) { console.log(e.message); }); Deferred.parallel([ function () { return wait(1); }, function () { return wait(2); } ]).next(function () { console.log('ok'); });
Deferred.connect(funo, options)
將一個函數封裝爲Deferred對象,其目的是融入現有的異步編程。
注意:Deferred.connect()和Deferred.earlier()方法同樣,並不會經過Deferred.define(obj)暴露給obj,它只能經過Deferred.connect()調用。官網使用了setTimeout的例子:
Deferred.connect()有兩種重載:
var timeout = Deferred.connect(setTimeout, { target: window, ok: 0 }); timeout(1).next(function () { alert('after 1 sec'); }); //另一種傳參 var timeout = Deferred.connect(window, "setTimeout"); timeout(1).next(function () { alert('after 1 sec'); });
Deferred.retry(retryCount, funcDeferred[ ,options])
調用retryCount次funcDeffered方法(返回值類型爲Deferred),直到觸發成功事件或超過嘗試次數爲止。
options參數是一個對象,{wait:number}指定每次調用等待的秒數。
注意:Deferred.retry()並不會經過Deferred.define(obj)暴露給obj,它只能經過Deferred.retry()調用。
Deferred.define(); Deferred.retry(3, function (number) {//Deferred.retry()方法是--i的方式實現的 console.log(number); return Deferred.next(function () { if (number ^ 1)//當number!=1的時候拋出異常,表示失敗,number==1的時候則讓它成功 throw new Error('error'); }); }).next(function () { console.log('linkFly');//=>linkFly });
從源碼這一行能夠看到做者重點照顧的是這些方法:
Deferred.methods = ["parallel", "wait", "next", "call", "loop", "repeat", "chain"];
其餘的方法或許做者也以爲有點勉強吧,在Deferred.define()中默認都沒有暴露那些API。
原本就想寫jsDeferred的API,結果讀完了源碼...篇幅緣由就不解讀源碼的,有興趣的能夠在下面的引用連接點過去看源碼,不含註釋未壓縮版源碼僅400行左右。
jsDeferred實現簡單,代碼通俗易懂,而API切割的很是容易上手,理念也容易理解,隨着它的知名度提高進而讓JavaScript異步編程備受矚目,在閱讀jsDeferred的時候,我老是在想這些前輩們當時苦苦思索走出JavaScript自留地的感受,從現代的眼光來看,相比Promise,可能jsDeferred的實現甚至於略顯青澀。這也讓我想起了Robert Nyman前輩最初編寫getElementByClassName(),然而在當時看來,足夠豔驚世界。
隨着JavaScript的興起,如今的咱們多喜歡四處扒來代碼匆匆粘貼完成咱們大多數的任務,逐漸的丟失了本身思考和挖掘代碼的能力。值得慶幸的是JavaScript正在凝結本身的精華,將來迢長路遠,與君共勉。
下一篇將會講解JavaScript異步編程的特性——控制反轉。