什么是JavaScript異步編程
這可能是個(gè)比較深的話題。何謂異步?
籠統(tǒng)地說(shuō),異步在javascript就是延時(shí)執(zhí)行。嚴(yán)格來(lái)說(shuō),javascript中的異步編程能力都是由BOM與DOM提供的,如setTimeout,XMLHttpRequest,還有DOM的事件機(jī)制,還有HTML5新增加的webwork, postMessage,等等很多。這些東西都有一個(gè)共同的特點(diǎn),就是擁有一個(gè)回調(diào)函數(shù),實(shí)現(xiàn)控制反轉(zhuǎn)。由于控制反轉(zhuǎn)是更深?yuàn)W的問題,這里不想展開。不過(guò)有點(diǎn)可以確認(rèn)的,回調(diào)函數(shù)的存在打斷了原來(lái)的執(zhí)行流程,讓它們自行在適當(dāng)?shù)臅r(shí)機(jī)出現(xiàn)并執(zhí)行,這是個(gè)非常便捷的模式。對(duì)比主動(dòng)式的輪詢,你就知它多么節(jié)能。在同步編程,代碼基本上自上向下執(zhí)行,在異步編程,一些代碼就要寫到回調(diào)函數(shù)中,如果代碼之間存在依賴,回調(diào)函數(shù)套回調(diào)函數(shù)的情況也不少見,這種套嵌結(jié)構(gòu)對(duì)以后的維護(hù)來(lái)說(shuō)簡(jiǎn)直是地獄。還有一種我們不得不面對(duì)的情況,try...catch無(wú)法捕捉幾毫秒之后發(fā)生的異常。另外,除了setTimeout外,異步編程基本上由事件機(jī)制承擔(dān)的,它們的回調(diào)函數(shù)什么時(shí)候發(fā)生基本上都是未知數(shù),可能由于后臺(tái)發(fā)生系統(tǒng)級(jí)錯(cuò)誤,無(wú)法再發(fā)出響應(yīng),或者,系統(tǒng)忙碌,一時(shí)半刻響應(yīng)不過(guò)來(lái),這兩種情況我們也必需提供一個(gè)策略,中斷這操作,也就是所謂的abort,這些都是異步編程的所要處理的課題。
- $.post("/foo.json", function (dataOfFoo) {//多層套嵌結(jié)構(gòu)的Ajax回調(diào)
 - $.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);//這里的異常無(wú)法捕獲
 - }
 
由于在javascript編程,隨時(shí)都碰到這樣的需求,因此實(shí)現(xiàn)相關(guān)輕捷的API是重中之重。正如上面所說(shuō),它只少要有以下功能,能儲(chǔ)存一組回調(diào)函數(shù)(domReary,多投事件,特效),在特定時(shí)刻中執(zhí)行所有回調(diào)函數(shù),如果發(fā)生錯(cuò)誤能觸發(fā)相應(yīng)的處理函數(shù)(負(fù)向回調(diào)),能中止整個(gè)操作,從中斷處再起操作,如果要求更多,我們還想能從串行轉(zhuǎn)向并行,由并行轉(zhuǎn)入串行。可能有許多概念大家聽不懂,是不是?但想弄個(gè)好的特效,這些都是必需的。如果玩過(guò)后端JS的人,一定聽說(shuō)過(guò)node.js,現(xiàn)在基本成為它的代名詞了。路由派發(fā),IO操作,都是異步的,事件驅(qū)動(dòng)的,為了實(shí)現(xiàn)優(yōu)雅的異步編程,大牛們忙得焦頭爛額,一個(gè)個(gè)方案被提出來(lái),如do.js. step.js, async.js, flow.js……,不是太雞肋,就是無(wú)法應(yīng)用于前端。因此我們需要一個(gè)適合于前端的方案。
有件事我們必需明白,你想到的,人家都早已研究過(guò)了,并且已給出解決方案。十大javascript框架之一,Mochikit,就從Python的Twisted庫(kù)搞來(lái)Deferred,后來(lái)又給dojo學(xué)去,現(xiàn)在你們又看到,相同的東西又出現(xiàn)在jQuery1.5上了。不過(guò),Mochikit的Deferred還有一個(gè)不為人知的分支,由日本大牛cho45搞出來(lái)(他同時(shí)也搞什么BigInt,跨瀏覽器Testing,名氣緊隨amachang、uupaa、edvakf、nanto之后),叫JSDeferred。先說(shuō)dojo那派系的(包括jQuery)的Deferred,一直處于無(wú)敵狀態(tài),與Common.js搞出一套規(guī)范,什么promises,then,when都是那時(shí)制定,jQuer基本全盤接受。另一分支,cho45的JSDeferred,構(gòu)思非常奇特,沒有使用數(shù)組來(lái)裝載回調(diào)函數(shù),而是通過(guò)setTimeout,image.onload, postMessage等異步機(jī)制巧妙地把維護(hù)列隊(duì)地工作道回瀏覽器自身,雖然有致命缺陷,但其易用性也被日本JS界所首肯,我的Deferred對(duì)象就從它的基本上發(fā)展過(guò)來(lái)的。Deferred這東西,我通常稱之為異步列隊(duì),因?yàn)樗鼈兊拇_是需要兩組由回調(diào)函接構(gòu)成的隊(duì)列,非常之形象。
在我們搬出異步列隊(duì)之前,讓我們看看普通的列隊(duì)是怎么實(shí)現(xiàn)延遲的。
- 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)
 - }
 - }
 
這樣調(diào)用它:
- var q = new Queue;
 - q.queue(function(){
 - log(1)
 - }).queue(function(){
 - log(2)
 - }).queue(function(){
 - log(3)
 - });
 - while(q.list.length){
 - q.dequeune();
 - }
 
但這是同步,想異步,我們需要用setTimeout,
- 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()
 
如大家所見,這樣寫絕對(duì)不友好。我們需要把setTimeout整到Queue類中去,另對(duì)queue做一些修改,不要只彈出一個(gè)函數(shù)進(jìn)行執(zhí)行,通常情況下會(huì)對(duì)列隊(duì)中的所有回調(diào)進(jìn)行操作的,如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,如果我們能自由控制每個(gè)回調(diào)的間隔,這對(duì)于做動(dòng)畫效果說(shuō),就變得非常簡(jiǎn)單了。但這Queue類相對(duì)我們最初定下的目標(biāo)來(lái)說(shuō),還是差得遠(yuǎn)。Ajax,多投事件,domReay將統(tǒng)統(tǒng)劃歸于它的麾下,因此它需要用一些適用性更強(qiáng)的API。用過(guò)dojo的人也知,它的Deferred就像DNA的染色體一樣,是雙線的,可以捕捉不在同一時(shí)間線上的異常,而且這些列隊(duì)不能像衛(wèi)生筷那樣用完一次就廢了,這樣就無(wú)法支撐多投事件的實(shí)現(xiàn)了。想要實(shí)現(xiàn)這些功能,就需要一個(gè)很復(fù)雜的東西,我將在第二部分隆重介紹我的異步列隊(duì),看它是如何優(yōu)雅地解決這些問題。
原文:http://www.cnblogs.com/rubylouvre/archive/2011/03/14/1982699.html
【編輯推薦】















 
 
 






 
 
 
 