这可能是一个更深层次的话题。什么是异步?一般来说,javascript中的异步意味着延迟执行。严格来说,javascript中的异步编程能力是由BOM和DOM提供的,比如setTimeout、XMLHttpRequest、DOM的事件机制,还有HTML5新加入的webwork、postMessage等等。这些东西有一个共同的特点,就是都有一个回调函数来实现控制反转。由于控制反转是一个比较深奥的问题,这里就不展开了。但是可以确认回调函数的存在打断了原来的执行过程,让它们在合适的时候出现并执行。这是一种非常方便的模式。与主动轮询相比,你可以看到它是多么的节能。在同步编程中,代码基本上是自上而下执行的。在异步编程中,有些代码必须写到回调函数中。如果代码之间存在依赖关系,回调函数嵌套在回调函数中的情况并不少见。这种嵌套结构是以后维护的地狱。还有一种情况我们要面对,try...catch无法捕捉到几毫秒后发生的异常。另外,除了setTimeout,异步编程基本都是由事件机制来承担的。他们的回调函数什么时候出现基本上是未知的。可能是后台出现系统级错误无法发出响应,或者系统繁忙。如果不能响应,我们还必须提供一种策略来中断这两种情况下的运行,也就是所谓的中止。这些都是异步编程要处理的主题。$.post("/foo.json",function(dataOfFoo){//多层嵌套结构的ajax回调$.post("/bar.json",function(dataOfBar){$.post("/baz.json",function(dataOfBaz){alert([dataOfFoo,dataOfBar,dataOfBaz]);});});});functionthrowError(){thrownewError('ERROR');}try{setTimeout(throwError,3000);}catch(e){alert(e);//这里的异常不能被捕获}由于在javascript编程中随时都会遇到这种需求,所以实现相关方便的API是重中之重。如上所述,它至少有以下功能,可以存储一组回调函数(domReary、多播事件、特效),在特定时刻执行所有回调函数,如果出错则触发相应的处理函数发生(负回调),它可以停止整个操作并从中断的地方恢复操作。如果需要更多,我们还希望能够从串行切换到并行,以及从并行切换到串行。可能有很多概念你不明白,对吧?但是要得到好的特效,这些都是必须的。如果你玩过后端JS,那你一定听说过node.js,现在基本就是它的代名词了。路由调度和IO操作都是异步和事件驱动的。为了实现优雅的异步编程,大牛们忙得不可开交,一一提出,比如do.js.step.js、async.js、flow.js...,要么太鸡肋,要么就是不能应用于前端。所以我们需要一个适合前端的解决方案。有一点我们必须明白,你想到的,人家已经研究过了,也给出了解决方案。十大javascript框架之一的Mochikit,是从Python的Twisted库中得到Deferred,后来为了dojo学习的,现在可以看到jQuery1.5上又出现了同样的东西。不过Mochikit的Deferred还有一个不为人知的分支,是日本大牛cho45创建的(他也做BigInt,跨浏览器Testing,名气跟在amachang、uupaa、edvakf、nanto之后),叫做JSDeferred。先说说dojo派(包括jQuery)的Deferred,一直以来都是立于不败之地。它开发了一套通用的规范。cho45的另一个分支JSDeferred有一个非常奇特的想法。它没有使用数组来加载回调函数,而是通过setTimeout、image.onload、postMessage等异步机制,巧妙地将维护队列的工作交还给浏览器自己。虽然它有一个致命的缺陷,但是它的易用性也得到了日本JS社区的认可,我的Deferred对象就是从它发展而来的。Deferred,我一般叫它异步队列,因为他们真的需要两个回调组成的队列,很形象。在我们离开异步队列之前,让我们看看常规队列如何实现延迟。varQueue=function(){this.list=[]}Queue.prototype={constructor:Queue,queue:function(fn){this.list.push(fn)returnthis;},dequeue:function(){varfn=this.list.shift()||function(){};fn.call(this)}}像这样调用它:varq=newQueue;q.queue(function(){log(1)}).queue(function(){log(2)}).queue(function(){log(3)});while(q.list.length){q.dequeune();}但这是同步的。如果我们要异步,就需要使用setTimeout:varel=document.getElementById("test");varq=newQueue();q.queue(function(){varself=this;el.innerHTML=1setTimeout(function(){self.dequeue()},1000);}).queue(function(){varself=this;el.innerHTML=2setTimeout(function(){self.dequeue()},1000);}).queue(function(){varself=this;el.innerHTML=3setTimeout(function(){self.dequeue()},1000);}).dequeue()可见,这么写肯定不友好。我们需要将setTimeout集成到Queue类中,对队列进行一些修改。不要只是弹出一个函数来执行。通常,它会对队列中的所有回调进行操作,例如domReay,多抛事件。varQueue=function(){this.list=[]}Queue.prototype={constructor:Queue,queue:function(fn){this.list.push(fn)returnthis;},wait:function(ms){this.list.push(ms)returnthis;},dequeue:function(){varself=this,list=self.list;varel=list.shift()||function(){};if(typeofel==“数字”){setTimeout(function(){self.dequeue();},el);}elseif(typeofel==“function”){el.call(this)if(list.length)self.dequeue();}}}太好了,如果我们可以自由控制每次回调的时间间隔,动画效果就变得很简单了。但是这个Queue类离我们最初的目标还差得很远。Ajax、多事件、domReay都会属于它,所以它需要使用一些适用性更强的API。用过dojo的人也知道,它的Deferred是双线程的,就像DNA染色体一样,可以捕捉到不在同一条时间线上的异常,而且这些队列不能像卫生筷子一样用完一次。不支持多播事件的实现。要实现这些功能,需要一个很复杂的东西。我将在第二部分介绍我的异步队列,看看它是如何优雅地解决这些问题的。
