-2

I can't understand why the for loop does not work properly

for (x = 0; x < 10; x++) { 
url= 'http://capc-pace.phac-aspc.gc.ca/details-eng.php?project='+x;
urllib.request(url, function (err, data, res) {
    if (err || res.statusCode !=200) {
    }
    else{
    console.log(x);
    }
});
}

I always get this as my response

s452:scripts 04101frt$ node javascriptparser.js 
10
10
10
10
10

I would expect to get all the pages that don't have redirect (i.e. their numbers in their url's)

The amount of numbers turn out fine (5 in this case) but I want to have their proper value.

stothek3
  • 13
  • 3
  • @GregHewgill, why? that one asks for why immediately invoking a function declaration is a syntax error. – André Werlang Jan 19 '15 at 02:02
  • With all due respect, I must insist, that linked question (http://stackoverflow.com/questions/1634268/explain-javascripts-encapsulated-anonymous-function-syntax) doesn't tell a thing about closures. That one is about syntax. This one is about semantics. And probably a dupe, but OP won't find any answer on that question. – André Werlang Jan 19 '15 at 02:35
  • This probably is the most suited duplicate. http://stackoverflow.com/questions/111102/how-do-javascript-closures-work This indeed is a duplicate of many for sur, since this kind of question popup a lot daily. – PSL Jan 19 '15 at 05:33

2 Answers2

1

So, you are using closures, ain't you?

Closure is the thing that capture the variable x from parent scope inside anonymous function. x will always refer to the last value assigned by that variable, even if the function that "created" the variable has long finished.

You need to pass a copy of the variable's value during each step. Something like this:

for (x = 0; x < 10; x++) { 
  url= 'http://capc-pace.phac-aspc.gc.ca/details-eng.php?project='+x;
  (function (x) {
    urllib.request(url, function (err, data, res) {
      if (err || res.statusCode !=200) {
      }
      else{
        console.log(x);
      }
    }
  })(x);
}

See what we have done? A new immediately invoked function expression (IIFE) receives a value for its argument. This way we effectively create 10 copies of the loop counter. console.log(x) refers to this parameter, not the outer loop counter.

André Werlang
  • 5,340
  • 29
  • 46
  • You could just abstract out the request call to a function which takes argument x and just invoke the function with x in the loop :) I believe that would probably be more clean and readable. Which you can achieve with forEach as well. – PSL Jan 19 '15 at 02:27
1

What you're running into here is caused by javascript's function level scope. Take a moment and read this - it explains really well what is going on http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html

Here's a fiddle to visualize the problem even more so -

http://jsfiddle.net/tm604oLu/

var url;
//wrong: because of being all on the same scope the value of x is 9 at the time it's evaluated
for (x = 0; x < 10; x++) {
    url = 'http://capc-pace.phac-aspc.gc.ca/details-eng.php?project=' + x;
    setTimeout(function () {
        console.log(url);
    }, 1);
}

//correct behavior: by passing the url as the parameter to a function you are creating a new 'scope' and the value does not get overwritten
for (x = 0; x < 10; x++) {
    url = 'http://capc-pace.phac-aspc.gc.ca/details-eng.php?project=' + x;
    (function (ownScope) { 
        setTimeout(function () {
            console.log(ownScope);
        }, 1);
    }(url));
}

By creating an IFFE (immediately invoked function expression) you create a new scope - which helps preserve the intended value for your counter.

Birgit Martinelle
  • 1,839
  • 1
  • 12
  • 9