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源碼剖析》):web
- Promise是基於狀態的
- 狀態標識:pending(初始狀態)、fulfilled(成功狀態)和rejected(失敗狀態)。
- 狀態爲單方向移動「pending->fulfilled」,」pending->rejected」。
- 因爲存在狀態標識,因此支持晚事件處理的晚綁定。
- 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(),這是兩種不一樣的東西:
摘錄源碼以下:
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內部會加特技,是假的一坨,是表面看起來一坨。加了特技以後,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次重載:
下面一張圖演示了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:
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
|