javascript 異步編程2

好像有這麼一句名言——"每一個優雅的接口,背後都有一個齷齪的實現"。最明顯的例子,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 <= 0){
if(fns instanceof Array){//如果是數組強制轉換爲數組
values.length = fns.length;
values
= A_slice.call(values)
}
self._fire(
"ok",[values])
}
}).once(
function(e){
self._fire(
"ng",[e])
});
obj.arr.push(fn);
count
++
})(key,fns[key])
}
}
self.onabort
= function(){
var i = 0, d;
while(d = obj.arr[i++]){
d.abort();
}
}
self._firing.push(obj);
return self
},
//處理相近的迭代操作
loop : function (obj, fn, result) {
obj
= {
begin : obj.begin
|| 0,
end : (
typeof obj.end == "number") ? obj.end : obj - 1,
step : obj.step
|| 1,
last :
false,
prev :
null
}
var step = obj.step,
_loop
= function(i,obj){
if (i <= obj.end) {
if ((i + step) > 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 <= obj.end) ? Deferred.get(this).then(_loop).fire(obj.begin,obj) : null;
}
}
//將原型方法轉換爲類方法
"loop wait then once paiallel".replace(//w+/g, function(method){
Deferred[method]
= Deferred.prototype[method]
});
})();

  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()

  上面幾個例子嚴格來說是同步執行,想實現異步就要用到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()

  好了,我們看異步列隊中最難的部分,並行操作。這相當於模擬線程了,兩個不相干的東西各自做自己的事,互不干擾。當然在時間我們還是能看出先後順序來的。擔當這職責的方法爲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)

  不過,上面並沒有用到異步,都是同步,這時,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);

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

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

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

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)
});

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

發佈了17 篇原創文章 · 獲贊 3 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章