原文連接:
7 lines JavaScript library for calling asynchronous functions
翻譯人員:
鐵錨
翻譯時間: 2014年02月18日
示例地址:
簡短強悍的JavaScript異步調用庫在線調試
對於博文
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);