JS 異步隊列的實現

這多是個比較深的話題。何謂異步?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的染色體同樣,是雙線的,能夠捕捉不在同一時間線上的異常,並且這些列隊不能像衛生筷那樣用完一次就廢了,這樣就沒法支撐多投事件的實現了。想要實現這些功能,就須要一個很複雜的東西,我將在第二部分隆重介紹個人異步列隊,看它是如何優雅地解決這些問題。數組

相關文章
相關標籤/搜索