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与动画效果说说。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值