2010-12-22 25 views
31

我剛剛開始使用node.js.我做了一些Ajax的東西,但沒有太複雜的東西,所以回調仍然在我頭上。我看着異步,但我需要的是順序運行幾個函數。瞭解javascript回調與node.js的概念,特別是在循環中

我基本上有一些東西,從API中拉出一些JSON,創建一個新的,然後做一些事情。顯然,我不能只運行它,因爲它一次運行所有內容並且具有空的JSON。大多數進程必須按順序運行,但是如果從API中提取JSON,它可以在等待時提取其他JSON,那麼很好。將回調放入循環時我感到困惑。我該如何處理索引?我想我已經看到一些在循環內部使用回調的地方作爲一種遞歸函數,並且根本不使用for循環。

簡單的例子會幫助很多。

+2

我會推薦閱讀[JavaScript Closure Notes](http://jibbering.com/faq/notes/closures/) - nodes.js只是按照規則播放。 – 2010-12-22 07:55:37

+2

@pst:恩,我見過好多了。例如,*「當這些內部函數之一被包含在函數之外時,就形成了一個閉包...」*這是錯誤的。當您創建函數,句號時會創建閉包。該函數是否可以在其包含的範圍之外被訪問是完全不相關的。它也開始談論關閉具有「潛在有害」的影響,雖然事實並非如此。 – 2010-12-22 13:13:06

回答

85

如果回調是在相同的範圍內定義的,則循環定義在(通常是這種情況)中,那麼回調將有權訪問索引變量。撇開細節的NodeJS了一會兒,讓我們考慮這樣的功能:

function doSomething(callback) { 
    callback(); 
} 

這個函數接受一個回調函數引用,它是所有調用它。不是很令人興奮。 :-)

現在讓我們使用,在一個循環:

var index; 

for (index = 0; index < 3; ++index) { 
    doSomething(function() { 
     console.log("index = " + index); 
    }); 
} 

(在計算密集型代碼  —像一個服務器進程  —最好不要從字面上做上面的生產代碼,我們」 LL回來在某一時刻)

現在,當我們運行,我們看到預期的輸出:

index = 0 
index = 1 
index = 2 

我們的回調能夠訪問index,因爲回調是關閉而不是其定義的範圍內的數據。 (不要擔心術語「關閉」 closures are not complicated。)

我之所以說這可能是最好不要從字面上做上述計算密集型的生產代碼,該代碼創建上每次迭代功能(禁止在編譯器中進行優化,V8非常聰明,但優化創建這些函數並非易事)。所以這裏有一個稍微返工例如:

var index; 

for (index = 0; index < 3; ++index) { 
    doSomething(doSomethingCallback); 
} 

function doSomethingCallback() { 
    console.log("index = " + index); 
} 

這可能看起來有點出人意料,但它仍然以同樣的方式,並且仍然具有相同的輸出,因爲doSomethingCallback仍然高於index封閉,所以它仍然看到了值爲index至於何時被調用。但現在只有一個doSomethingCallback函數,而不是每個循環中的新鮮函數。

現在,讓我們一個反面的例子,一些工作:

foo(); 

function foo() { 
    var index; 

    for (index = 0; index < 3; ++index) { 
     doSomething(myCallback); 
    } 
} 

function myCallback() { 
    console.log("index = " + index); // <== Error 
} 

失敗,因爲myCallback不是在同一範圍內(或嵌套的範圍內)定義index是在定義in,所以indexmyCallback內未定義。

最後,讓我們考慮在循環中設置事件處理程序,因爲必須注意這一點。在這裏,我們將深入一個的NodeJS位:

var spawn = require('child_process').spawn; 

var commands = [ 
    {cmd: 'ls', args: ['-lh', '/etc' ]}, 
    {cmd: 'ls', args: ['-lh', '/usr' ]}, 
    {cmd: 'ls', args: ['-lh', '/home']} 
]; 
var index, command, child; 

for (index = 0; index < commands.length; ++index) { 
    command = commands[index]; 
    child = spawn(command.cmd, command.args); 
    child.on('exit', function() { 
     console.log("Process index " + index + " exited"); // <== WRONG 
    }); 
} 

似乎像上面應該工作相同的方式,我們前面的循環一樣,但有一個關鍵的區別。在我們之前的循環中,回調被立即調用,所以它看到了正確的index值,因爲index還沒有機會繼續前進。但是,在上面,我們將在調用回調之前通過循環。結果?我們看到

Process index 3 exited 
Process index 3 exited 
Process index 3 exited 

這是一個關鍵點。封閉沒有副本它關閉的數據,它有一個現場參考它。因此,在每個進程的exit回調得到運行時,循環將已完成,因此所有三個調用看到相同的index值(其值爲循環的端點)。

我們可以通過具有回調使用不同變量不會改變,這樣解決這個問題:

var spawn = require('child_process').spawn; 

var commands = [ 
    {cmd: 'ls', args: ['-lh', '/etc' ]}, 
    {cmd: 'ls', args: ['-lh', '/usr' ]}, 
    {cmd: 'ls', args: ['-lh', '/home']} 
]; 
var index, command, child; 

for (index = 0; index < commands.length; ++index) { 
    command = commands[index]; 
    child = spawn(command.cmd, command.args); 
    child.on('exit', makeExitCallback(index)); 
} 

function makeExitCallback(i) { 
    return function() { 
     console.log("Process index " + i + " exited"); 
    }; 
} 

現在我們輸出正確的值(以任何順序流程出口):

Process index 1 exited 
Process index 2 exited 
Process index 0 exited 

是工作的方式是,我們分配給exit事件回調關閉了在我們對makeExitCallback呼叫的i說法。所述第一回調makeExitCallback創建並返回關閉在該呼叫的imakeExitCallback,它創建的第二個回調關閉超過用於i調用makeExitCallback(這是比前面呼叫i值不同),等等。

如果你給the article linked above一個閱讀,一些事情應該更清楚。文章中的術語有點過時(ECMAScript 5使用更新的術語),但概念沒有改變。