短小強悍的JavaScript非同步呼叫庫

鐵錨發表於2014-02-21

  對於博文 20行完成一個JavaScript模板引擎 的備受好評我感到很驚訝,並決定用此文章介紹使用我經常使用的另一個小巧實用的工具.我們知道,在瀏覽器中的 JavaScript 絕大部分的操作都是非同步的(asynchronous),所以我們一直都需要使用回撥方法,而有時不免陷入回撥的泥淖而欲死欲仙。

  假設我們有兩個 functions ,我們順序地在一個後面執行完後呼叫另一個。他們都操作同一個變數。第一個設定它的值,第二個使用它的值。

var value;  
var A = function() {  
    setTimeout(function() {  
        value = 10;  
    }, 200);  
}  
var B = function() {  
    console.log(value);  
}

  那麼,現在如果我們執行 A();B(); 我們將在控制檯看到輸出為 undefined . 之所以會這樣是因為 A 函式使用了非同步方式設定 value 。我們能做的就是傳一個回撥函式給A,並讓函式A在執行完後執行回撥函式。

var value;  
var A = function(callback) {  
  setTimeout(function() {  
    value = 10;  
    callback();  
  }, 200);  
};  
var B = function() {  
  console.log(value);  
};  

A(function() {  
  B();  
});

  這樣確實有用,但想象一下加入我們需要執行5個或更多方法時將會發生什麼。一直傳遞迴調函式將會導致混亂和非常不雅觀的程式碼。
好的解決辦法是寫一個工具函式,接受我們的程式並控制整個過程。讓我們先從最簡單的開始:

var queue = function(funcs) {  
    // 接下來請看,董卿???  
}

  接著,我們要做的是通過傳遞A和B來執行該函式 - queue([A, B])。我們需要取得第一個函式並執行它。

var queue = function(funcs) {  
    var f = funcs.shift();  
    f();  
}

  如果執行這段程式碼,您將看到一個 TypeError: undefined is not a function。這是因為 A函式沒收到回撥引數但卻試圖執行它。讓我們換一種呼叫方法。

var queue = function(funcs) {  
    var next = function() {  
        // ...  
    };  
    var f = funcs.shift();  
    f(next);  
};

  在 A執行完後會呼叫 next 方法。將下一步操作放在 next 函式列表中是個很好的做法。我們可以將程式碼歸攏在一起,而且我們能夠傳遞整個陣列(即便陣列中有很多函式等待執行)。

var queue = function(funcs) {  
    var next = function() {  
        var f = funcs.shift();  
        f(next);  
    };  
    next();  
};

  到了這一步,我們基本上達到了我們的目標。即函式A 執行後,接著會呼叫 B,列印出變數的正確值。這裡的關鍵是 shift 方法的使用。它刪除陣列的第一個元素並返回該元素。一步一步執行下去, funcs陣列就會變成 empty(空的)。所以,這可能會導致另一個錯誤。為了證明這一觀點,讓我們假設我們仍然需要執行這兩個功能,但我們不知道他們的順序。在這種情況下,兩個函式都應該接受回撥引數(callback )並執行它。

var A = function(callback) {  
    setTimeout(function() {  
        value = 10;  
        callback();  
    }, 200);  
};  
var B = function(callback) {  
    console.log(value);  
    callback();  
};

  當然,我們會得到 TypeError: undefined is not a function.
要阻止這一點,我們應該檢查funcs陣列是否為空。

var queue = function(funcs) {  
    (function next() {  
        if(funcs.length > 0) {  
            var f = funcs.shift();  
            f(next);  
        }  
    })();  
};

  我們所做的就是定義 next 函式並呼叫它。這種寫法減少了一點程式碼。

  讓我們試著想象儘可能多的情況。比如當前執行功能的 scope 。函式內的 this 關鍵字可能指向了全球的 window 物件。,如果我們可以設定自己的scope 當然是件很酷的事情。

var queue = function(funcs, scope) {  
    (function next() {  
          if(funcs.length > 0) {  
              var f = funcs.shift();  
              f.apply(scope, [next]);  
          }  
    })();  
};

  我們為這個tiny 類庫增加了一個引數。接著我們使用 apply  函式,而不是直接呼叫 f(next),來設定scope 並將引數 next 傳遞進去。同樣的功能,但漂亮多了。

  我們需要的最後一個特性,就是是函式間傳遞引數的能力。當然我們不知道具體會有多少引數將被使用。這就是為什麼我們需要使用 arguments 變數的原因。你可能知道,該變數在每個 JavaScript函式中都是可用的,代表了傳進來的引數。它就和一個陣列差不多,但不完全是。因為在 apply 方法中我們需要使用真正的陣列,使用一個小竅門來進行轉換。

var queue = function(funcs, scope) {  
    (function next() {  
          if(funcs.length > 0) {  
              var f = funcs.shift();  
              f.apply(scope, [next].concat(Array.prototype.slice.call(arguments, 0)));  
          }  
    })();  
};

  下面是測試的程式碼:

// 測試程式碼  
var obj = {  
    value: null  
};  

queue([  
    function(callback) {  
        var self = this;  
        setTimeout(function() {  
            self.value = 10;  
            callback(20);  
        }, 200);  
    },  
    function(callback, add) {  
        console.log(this.value + add);  
        callback();  
    },  
    function() {  
        console.log(obj.value);  
    }  
], obj);

  執行後的輸出為:

30  
10

  為了程式碼的可讀性和美觀,我們將部分相關的程式碼移到一行內:

var queue = function(funcs, scope) {  
    (function next() {  
          if(funcs.length > 0) {  
              funcs.shift().apply(scope || {}, [next].concat(Array.prototype.slice.call(arguments, 0)));  
          }  
    })();  
};

  你可以 點選這裡檢視並除錯相關程式碼 ,完整的測試程式碼如下:

var queue = function(funcs, scope) {  
    (function next() {  
          if(funcs.length > 0) {  
              funcs.shift().apply(scope || {}, [next].concat(Array.prototype.slice.call(arguments, 0)));  
          }  
    })();  
};  

var obj = {  
    value: null  
};  

queue([  
    function(callback) {  
        var self = this;  
        setTimeout(function() {  
            self.value = 10;  
            callback(20);  
        }, 200);  
    },  
    function(callback, add) {  
        console.log(this.value + add);  
        callback();  
    },  
    function() {  
        console.log(obj.value);  
    }  
], obj);

  原文出處: krasimirtsonev

相關文章