JavaScript异步编程(2)-先驱者:jsDeferred
JavaScript当前有众多实现异步编程的⽅式,最为耀眼的就是ECMAScript 6规范中的Promise对象,它来⾃于CommonJS⼩组的努⼒:Promise/A+规范。
研究javascript的异步编程,jsDeferred也是有必要探索的:因为Promise/A+规范的制定基本上是奠定在jsDeferred上,它是javascript异步编程中⾥程碑式的作品。jsDeferred⾃⾝的实现也是⾮常有意思的。
本⽂将探讨项⽬jsDeferred的模型,带我们感受⼀个不⼀样的异步编程体验和实现。
> 本⽂内容如下: > > - jsDeferred和Promise/A+ > - jsDeferred的⼯作模型 > - jsDeferred API > - 参考和引⽤
在上⼀篇⽂章《》中,我们讨论了ECMAScript 6的Promise对象,这⼀篇我们来看javascript异步编程的先驱者——jsDeferred。
是⽇本javascript⾼⼿受MochiKit.Async.Deferred模块启发在2007年开发(07年就在玩这个了...)的⼀个异步执⾏类库。我们将jsDeferred的原型和()进⾏对⽐(来⾃_肥仔John的《》):
Promise是基于状态的
状态标识:pending(初始状态)、fulfilled(成功状态)和rejected(失败状态)。
状态为单⽅向移动“pending->fulfilled","pending->rejected"。
由于存在状态标识,所以⽀持晚事件处理的晚绑定。
jsDeferred是基于事件的,并没有状态标识
实例的成功/失败事件是基于事件触发⽽被调⽤
因为没有状态标识,所以可以多次触发成功/失败事件
不⽀持晚绑定
下⾯⼀张图粗略演⽰了jsDeferred的⼯作模型。
jsDeferred Model
下⾯涉及到jsDeferred的源码,对于第⼀次接触的童鞋请直接拉到API⼀节(下⼀节),读完了API再来看这⾥。
jsDeferred第⼀次调⽤next有着不同的处理,jsDeferred在第⼀次调⽤next()的时候,会⽴即异步执⾏这个回调函数——⽽这个挂起异步,则视当前的环境(如浏览器最佳环境)选择最优的异步挂起⽅案,例如现代浏览器下会通过创建Image对象的⽅式来进⾏异步挂起,摘录源码如下:
<_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.src = "data:image/png," + Math.random();
if (fun) d.callback.ok = fun;
return d;
};
Deferred对象的静态⽅法 - ()源码:
< =
<_faster_way_readystatechange ||//IE下使⽤onreadystatechange()
<_faster_way_Image ||//现代浏览器下使⽤Image对象onload/onerror事件
<_tick ||//Node下使⽤Tick()
<_default;//默认使⽤setTimeout
我们务必要理清()和(),这是两种不同的东西:
<()的职责是压⼊异步的代码,并⽴即异步执⾏的。
()是从上⼀个Deferred对象链中构建的Deferred。当没有上⼀个Deferred链的
时候,它并不会执⾏next()中压⼊的函数,它的执⾏继承于上⼀个Deferred触发的事件或⾃⾝事件的触发[ call / fail ]。
摘录源码如下:
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 (r) r(e);
}
if (Deferred.isDeferred(value)) {
//在这⾥,和_post()呼应,调⽤Deferred链的下⼀个Deferred对象
value._next = this._next;
} else {
if (this._next) this._next._fire(next, value);
}
return this;
}
}
再⼀次强调,务必搞清楚()和()。
当我第⼀次知道jsDeferred API有⼀坨的时候,其实我是,是拒绝的。我跟你讲,我拒绝,因为其实我觉得这根本要不了⼀坨,但正妹跟我讲,jsDeferred内部会加特技,是假的⼀坨,是表⾯看起来⼀坨。加了特技之后,jsDeferred duang~duang~duang~,很酷,很炫,很酷炫。
jsDeferred的API众多,因为jsDeferred把所有的异步问题都划分到了最⼩的粒⼦,这些API相互进⾏组合则可以完成逆天的异步能⼒,在后续的API⽰例中可以看到jsDeferred API组合从⽽完成强⼤的异步编程。我们在阅读jsDeferred的API的时候应该时刻思考如果使⽤ES6的Promise对象⼜该如何去处理,阅读应该是⼤脑的盛宴。
貌似没有看到过jsDeferred的详细的中⽂API⽂档(),就这⾥顺便整理⼀份简单的出来(虽然它的API已经⾜够通俗易懂了)。值得⼀提的是官⽹的API引导例⼦⾮常的⽣动和实⽤:
Deferred()/new Deferred ()
构造函数(constructor),创建⼀个Deferred对象。
var defer = Deferred();//或new Deferred()
//创建⼀个Deferred对象
<(function () {
console.log('ok');
}).error(function (text) {
console.log(text);//=> linkFly
}).fail('linkFly');
实例⽅法
和Deferred.prototype.call
()构建⼀个全新的Deferred对象,并为它绑定成功事件处理函数,在没有调⽤Deferred.prototype.call()之前这个事件处理函数并不会执⾏。
var deferred = Deferred();
<(function (value) {
console.log(value); // => linkFly
}).call('linkFly');
和Deferred.prototype.fail
()构建⼀个全新的Deferred对象,并为它绑定失败事件处理函数,在没有调⽤Deferred.prototype.fail()之前这个事件处理函数并不会执⾏。
var deferred = Deferred();
<(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对象下
<(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!');// => 先输出
<(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对象(通过()),然后并⾏触发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请求。
Deferred.parallel
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 () {
return1;
}),
bar: wait(2).next(function () {
return2;
})
}).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
Deferred.define();
Deferred.earlier(
wait(2).next(function () { return'cnblog'; }),
js合并两个数组wait(1).next(function () { return'linkFly' })//1s后执⾏成功
).next(function (values) {
console.log(values);// 1s后 => [undefined, "linkFly"]
});
循环执⾏fun⽅法n次,若fun的执⾏事件超过20毫秒则先将UI线程的控制权交出,等⼀会⼉再执⾏下⼀轮的循环。
⾃⼰跑了⼀下,跑出问题来了...求道友指点下迷津
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(
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论