JavaScript異步編程(2)- 先驅者:jsDeferred

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
  • 參考和引用

jsDeferred和Promise/A+

在上一篇文章《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源碼剖析》):web

Promise/A+

  • Promise是基於狀態的
  • 狀態標識:pending(初始狀態)、fulfilled(成功狀態)和rejected(失敗狀態)。
  • 狀態爲單方向移動「pending->fulfilled」,」pending->rejected」。
  • 因爲存在狀態標識,因此支持晚事件處理的晚綁定。

jsDeferred

  • jsDeferred是基於事件的,並無狀態標識
  • 實例的成功/失敗事件是基於事件觸發而被調用
  • 由於沒有狀態標識,因此能夠屢次觸發成功/失敗事件
  • 不支持晚綁定

jsDeferred的工做模型

下面一張圖粗略演示了jsDeferred的工做模型。ajax

下面涉及到jsDeferred的源碼,對於第一次接觸的童鞋請直接拉到API一節(下一節),讀完了API再來看這裏。編程

jsDeferred第一次調用next有着不一樣的處理,jsDeferred在第一次調用next()的時候,會當即異步執行這個回調函數——而這個掛起異步,則視當前的環境(如瀏覽器最佳環境)選擇最優的異步掛起方案,例如現代瀏覽器下會經過建立Image對象的方式來進行異步掛起,摘錄源碼以下:segmentfault

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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()源碼:api

1
2
3
4
5
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.next()的職責是壓入異步的代碼,並當即異步執行的。
  • Deferred.prototype.next()是從上一個Deferred對象鏈中構建的Deferred。當沒有上一個Deferred鏈的時候,它並不會執行next()中壓入的函數,它的執行繼承於上一個Deferred觸發的事件或自身事件的觸發[ call / fail ]。

摘錄源碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 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對象。

1
2
3
4
5
6
7
8
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()以前這個事件處理函數並不會執行。

1
2
3
4
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()以前這個事件處理函數並不會執行。

1
2
3
4
var deferred = Deferred();
deferred.error( function () {
     console.log( 'error' ); // => error
}).fail();

靜態方法。Deferred全部的靜態方法,均可以使用Deferred.方法名()的方式調用。 Deferred.define(obj, list)

暴露靜態方法到obj上,無參的狀況下obj是全局對象:侵入性極強,但使用方便。list是一組方法,這組方法會同時註冊到obj上。

1
2
3
4
5
6
7
8
9
10
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對象)。

1
2
3
Deferred.define();
console.log(Deferred.isDeferred({})); //=> false
console.log(Deferred.isDeferred(wait(2))); //=> true

 

Deferred.call(fn[,args]*)

建立一個Deferred實例,而且觸發其成功事件。fn是成功後要執行的函數,後續的參數表示傳遞給fn的參數。

1
2
3
4
call( function (text) {
     console.log(text); //=> linkFly
}, 'linkFly' );
console.log( 'hello,world!' ); // => 先輸出

 

Deferred.next(fn)

建立一個Deferred實例,而且觸發其成功事件。fn是成功後要執行的函數,它等同於只有一個參數的call,即:Deferred.call(fn)

1
2
3
4
5
6
7
8
9
10
11
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!」。

1
2
3
4
5
6
7
8
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()也是異步的。

1
2
3
4
5
6
7
8
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次重載:

  • parallel(fn[ ,fn]*):傳入Function類型的參數,容許多個
  • parallel(Array):給定一個由Function組成的Array類型的參數
  • parallel(Object):給定一個對象,由對象中全部可枚舉的Function構建Deferred

 

下面一張圖演示了Deferred.parallel的工做模型,它能夠理解爲合併了3次ajax請求。

1
2
3
4
5
6
7
8
9
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傳遞的參數是一個對象的時候,返回值則是一個對象:

1
2
3
4
5
6
7
8
9
10
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.earlier(Deferred[ ,Deferred]*):傳入Deferred類型的參數,容許多個
  • Deferred.earlier(Array):給定一個由Deferred組成的Array類型的參數
  • Deferred.earlier(Object):給定一個對象,由對象中全部可枚舉的Deferred構建Deferred

 

1
2
3
4
5
6
7
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…求道友指點下迷津

1
2
3
4
5
6
7
8
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類型的參數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Deferred.define();
chain(
     function () {
         console.log( 'start' );
     },
     function () {
         console.log( 'linkFly' );
     }
);
 
//等同於
next( function () {
     console.log( 'start' );
}).next( function () {
     console.log( 'linkFly' );
});

它經過函數名來判斷函數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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()的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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' );
});

固然能夠組合參數:

1
2
3
4
5
6
相關文章
相關標籤/搜索