Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>If the callback is defined in the same scope the loop is defined in (which is frequently the case), then the callback will have access to the index variable. Leaving aside NodeJS particulars for a moment, let's consider this function:</p> <pre><code>function doSomething(callback) { callback(); } </code></pre> <p>That function accepts a callback function reference and all it does is call it. Not very exciting. :-)</p> <p>Now let's use that in a loop:</p> <pre><code>var index; for (index = 0; index &lt; 3; ++index) { doSomething(function() { console.log("index = " + index); }); } </code></pre> <p><em>(In compute-intensive code&nbsp;&mdash; like a server process&nbsp;&mdash; best not to literally do the above in production code, we'll come back to that in a moment.)</em></p> <p>Now, when we run that, we see the expected output:</p> <pre><code>index = 0 index = 1 index = 2 </code></pre> <p>Our callback was able to access <code>index</code>, because the callback is a <em>closure</em> over the data in scope where it's defined. (Don't worry about the term "closure," <a href="http://blog.niftysnippets.org/2008/02/closures-are-not-complicated.html" rel="noreferrer">closures are not complicated</a>.)</p> <p>The reason I said it's probably best not to literally do the above in compute-intensive production code is that the code creates a function on <em>every iteration</em> (barring fancy optimization in the compiler, and V8 is very clever, but optimizing out creating those functions is non-trivial). So here's a slightly reworked example:</p> <pre><code>var index; for (index = 0; index &lt; 3; ++index) { doSomething(doSomethingCallback); } function doSomethingCallback() { console.log("index = " + index); } </code></pre> <p>This may look a bit surprising, but it still works the same way, and still has the same output, because <code>doSomethingCallback</code> is still a closure over <code>index</code>, so it still sees the value of <code>index</code> as of when it's called. But now there's only one <code>doSomethingCallback</code> function, rather than a fresh one on every loop.</p> <p>Now let's take a negative example, something that <strong>doesn't</strong> work:</p> <pre><code>foo(); function foo() { var index; for (index = 0; index &lt; 3; ++index) { doSomething(myCallback); } } function myCallback() { console.log("index = " + index); // &lt;== Error } </code></pre> <p>That fails, because <code>myCallback</code> is not defined in the same scope (or a nested scope) that <code>index</code> is in defined in, and so <code>index</code> is undefined within <code>myCallback</code>.</p> <p>Finally, let's consider setting up event handlers in a loop, because one has to be careful with that. Here we will dive into NodeJS a bit:</p> <pre><code>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 &lt; commands.length; ++index) { command = commands[index]; child = spawn(command.cmd, command.args); child.on('exit', function() { console.log("Process index " + index + " exited"); // &lt;== WRONG }); } </code></pre> <p>It <em>seems</em> like the above should work the same way that our earlier loops did, but there's a crucial difference. In our earlier loops, the callback was being called immediately, and so it saw the correct <code>index</code> value because <code>index</code> hadn't had a chance to move on yet. In the above, though, we're going to spin through the loop before the callback is called. The result? We see</p> <pre><code>Process index 3 exited Process index 3 exited Process index 3 exited </code></pre> <p>This is a crucial point. A closure doesn't have a <em>copy</em> of the data it closes over, it has a <em>live reference</em> to it. So by the time the <code>exit</code> callback on each of those processes gets run, the loop will already be complete, so all three calls see the same <code>index</code> value (its value as of the <em>end</em> of the loop).</p> <p>We can fix this by having the callback use a <em>different</em> variable that won't change, like this:</p> <pre><code>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 &lt; 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"); }; } </code></pre> <p>Now we output the correct values (in whatever order the processes exit):</p> <pre><code>Process index 1 exited Process index 2 exited Process index 0 exited </code></pre> <p>The way that works is that the callback we assign to the <code>exit</code> event closes over the <code>i</code> argument in the call we make to <code>makeExitCallback</code>. The first callback that <code>makeExitCallback</code> creates and returns closes over the <code>i</code> value for that call to <code>makeExitCallback</code>, the second callback it creates closes over the <code>i</code> value for <em>that</em> call to <code>makeExitCallback</code> (which is different than the <code>i</code> value for the earlier call), etc.</p> <p>If you give <a href="http://blog.niftysnippets.org/2008/02/closures-are-not-complicated.html" rel="noreferrer">the article linked above</a> a read, a number of things should be clearer. The terminology in the article is a bit dated (ECMAScript 5 uses updated terminology), but the concepts haven't changed.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload