这多是个比较深的话题。何谓异步?javascript
笼统地说,异步在javascript就是延时执行。严格来讲,javascript中的异步编程能力都是由BOM与DOM提供的,如setTimeout,XMLHttpRequest,还有DOM的事件机制,还有HTML5新增长的webwork, postMessage,等等不少。这些东西都有一个共同的特色,就是拥有一个回调函数,实现控制反转。因为控制反转是更深奥的问题,这里不想展开。不过有点能够确认的,回调函数的存在打断了原来的执行流程,让它们自行在适当的时机出现并执行,这是个很是便捷的模式。对比主动式的轮询,你就知它多么节能。在同步编程,代码基本上自上向下执行,在异步编程,一些代码就要写到回调函数中,若是代码之间存在依赖,回调函数套回调函数的状况也很多见,这种套嵌结构对之后的维护来讲简直是地狱。还有一种咱们不得不面对的状况,try...catch没法捕捉几毫秒以后发生的异常。另外,除了setTimeout外,异步编程基本上由事件机制承担的,它们的回调函数何时发生基本上都是未知数,可能因为后台发生系统级错误,没法再发出响应,或者,系统忙碌,一时半刻响应不过来,这两种状况咱们也必需提供一个策略,中断这操做,也就是所谓的abort,这些都是异步编程的所要处理的课题。前端
$.post( " /foo.json " , function (dataOfFoo) { // 多层套嵌结构的Ajax回调
$.post( " /bar.json " , function (dataOfBar) {
$.post( " /baz.json " , function (dataOfBaz) {
alert([dataOfFoo, dataOfBar, dataOfBaz]);
});
});
});
function throwError(){
throw new Error( ' ERROR ' );
}
try {
setTimeout(throwError, 3000 );
} catch (e){
alert(e); // 这里的异常没法捕获
}
因为在javascript编程,随时都碰到这样的需求,所以实现相关轻捷的API是重中之重。正如上面所说,它只少要有如下功能,能储存一组回调函数(domReary,多投事件,特效),在特定时刻中执行全部回调函数,若是发生错误能触发相应的处理函数(负向回调),能停止整个操做,从中断处再起操做,若是要求更多,咱们还想能从串行转向并行,由并行转入串行。可能有许多概念你们听不懂,是否是?但想弄个好的特效,这些都是必需的。若是玩事后端JS的人,必定据说过node.js,如今基本成为它的代名词了。路由派发,IO操做,都是异步的,事件驱动的,为了实现优雅的异步编程,大牛们忙得焦头烂额,一个个方案被提出来,如do.js. step.js, async.js, flow.js……,不是太鸡肋,就是没法应用于前端。所以咱们须要一个适合于前端的方案。java
有件事咱们必需明白,你想到的,人家都早已研究过了,而且已给出解决方案。十大javascript框架之一,Mochikit,就从Python的Twisted库搞来Deferred,后来又给dojo学去,如今大家又看到,相同的东西又出如今jQuery1.5上了。不过,Mochikit的Deferred还有一个鲜为人知的分支,由日本大牛cho45搞出来(他同时也搞什么BigInt,跨浏览器Testing,名气紧随amachang、uupaa、edvakf、nanto以后),叫JSDeferred。先说dojo那派系的(包括jQuery)的Deferred,一直处于无敌状态,与Common.js搞出一套规范,什么promises,then,when都是那时制定,jQuer基本全盘接受。另外一分支,cho45的JSDeferred,构思很是奇特,没有使用数组来装载回调函数,而是经过setTimeout,image.onload, postMessage等异步机制巧妙地把维护列队地工做道回浏览器自身,虽然有致命缺陷,但其易用性也被日本JS界所首肯,个人Deferred对象就从它的基本上发展过来的。Deferred这东西,我一般称之为异步列队,由于它们的确是须要两组由回调函接构成的队列,很是之形象。node
在咱们搬出异步列队以前,让咱们看看普通的列队是怎么实现延迟的。web
var Queue = function (){
this .list = []
}
Queue.prototype = {
constructor:Queue,
queue: function (fn) {
this .list.push(fn)
return this ;
},
dequeue: function (){
var fn = this .list.shift() || function (){};
fn.call( this )
}
}
这样调用它:编程
var q = new Queue;
q.queue( function (){
log( 1 )
}).queue( function (){
log( 2 )
}).queue( function (){
log( 3 )
});
while (q.list.length){
q.dequeune();
}
但这是同步,想异步,咱们须要用setTimeout:json
var el = document.getElementById( " test " );
var q = new Queue();
q.queue( function (){
var self = this ;
el.innerHTML = 1
setTimeout( function (){
self.dequeue()
}, 1000 );
}).queue( function (){
var self = this ;
el.innerHTML = 2
setTimeout( function (){
self.dequeue()
}, 1000 );
}).queue( function (){
var self = this ;
el.innerHTML = 3
setTimeout( function (){
self.dequeue()
}, 1000 );
}).dequeue()
如你们所见,这样写绝对不友好。咱们须要把setTimeout整到Queue类中去,另对queue作一些修改,不要只弹出一个函数进行执行,一般状况下会对列队中的全部回调进行操做的,如domReay,多投事件。后端
var Queue = function (){
this .list = []
}
Queue.prototype = {
constructor:Queue,
queue: function (fn) {
this .list.push(fn)
return this ;
},
wait: function (ms){
this .list.push(ms)
return this ;
},
dequeue: function (){
var self = this , list = self.list;
var el = list.shift() || function (){};
if ( typeof el == " number " ){
setTimeout( function (){
self.dequeue();
},el);
} else if ( typeof el == " function " ) {
el.call( this )
if (list.length)
self.dequeue();
}
}
}
Great,若是咱们能自由控制每一个回调的间隔,这对于作动画效果说,就变得很是简单了。但这Queue类相对咱们最初定下的目标来讲,仍是差得远。Ajax,多投事件,domReay将通通划归于它的麾下,所以它须要用一些适用性更强的API。用过dojo的人也知,它的Deferred就像DNA的染色体同样,是双线的,能够捕捉不在同一时间线上的异常,并且这些列队不能像卫生筷那样用完一次就废了,这样就没法支撑多投事件的实现了。想要实现这些功能,就须要一个很复杂的东西,我将在第二部分隆重介绍个人异步列队,看它是如何优雅地解决这些问题。数组