-1

I have a Node.js application that uses the request library to GET a number of URLs. The code is something like this:

for(var i = 0; i < docs.length; i++){

        var URL = docs[i].url;

        request(URL, function(error, response, html){
            console.log(URL);
            // Other code...
}

For the sake of simplicity, let’s say docs contain URLs like [URL1, URL2, URL3, ...]. With the first iteration, URL = URL1 and a request is sent to that URL. With the second iteration, a request is sent to URL2 and so on. However, at the end of the loop, URL = URLn. Inside the event complete function, when I log URL, I always get URLn. However, I need to able to get the respective URLs that is [URL1, URL2, URL3, ...].

Any idea, how I can maintain a local copy of the URL, that remains unchanged event when the global URL gets changed?

This must be something easy, but I can't figure it out.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ibrahim
  • 837
  • 2
  • 13
  • 24

4 Answers4

1

Basically, what you experience is normal behavior in JavaScript and no special behavior of Node.js.

The only thing in JavaScript that defines a scope are functions, and functions can access their own scope as well as any "outer" scopes.

Hence, the solution is to wrap your code in a function that takes the global variable as a parameter and provides it to the code within the function as a parameter: This one is evaluated for each call of the function, hence your inner code will get its own "copy".

Basically you have two options. Either use an immediate executed function expression. This is basically nothing but a name-less (i.e. anonymous) function that is called immediately where it is defined:

for(var i = 0; i < docs.length; i++){
  (function (url) {
    request(url, function(error, response, html){
      console.log(url); 
    });
  })(doc.url);
}

Or use the built-in forEach function of an array which automatically wraps its body in a function (which results in the same effect):

docs.forEach(function (url) {
  request(url, function(error, response, html){
    console.log(url); 
  });
});
Golo Roden
  • 140,679
  • 96
  • 298
  • 425
1

You should read about closures in JavaScript here.

Meanwhile, in simpler terms, the value of i would reach n at the end of all iterations. Hence rightly you would get URLn every time. If you wrap the request inside a immediately invoked function expression, you are creating another level in the scope chain. By doing so, the callback to request method won't refer to the variable i in global scope, but to the variable i that was available in the scope of the function at the time of sending the request. And that value of i you expect.

Code would be something like this then:

for(var i = 0; i < docs.length; i++){
    var URL = docs[i].url;

    (function(currentURL) {
        //now the URL is preserved as currentURL inside the scope of the function
        request(currentURL, function(error, response, html){
            console.log(currentURL);
            // This value of currentURL is the one that was available in the scope chain
            // Other code...
        });
    })(URL);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rahul Nanwani
  • 1,267
  • 1
  • 10
  • 21
0

Just wrap the code in a function or use forEach. This happens because of closure scope.

docs.forEach(functiom(doc) {
    var URL = doc.url;

    request(URL, function(error, response, html){
         console.log(URL);
         // Other code...
    })
});

Another fix

for(var i = 0; i < docs.length; i++){
    makeRequest(docs[i]);
}

function makeRequest(doc) {
    var URL = doc.url;

    request(URL, function(error, response, html){
        console.log(URL);
    });
}

And another a bit more uglier fix with a closure inside the for loop

for(var i = 0; i < docs.length; i++){
    (function(doc) {
        var URL = doc.url;

        request(URL, function(error, response, html){
            console.log(URL);
            // Other code...
        });
    })(docs[i]);
}

If you use something like JSHint, it will warn you not to create functions inside for loops as it will cause problems like this.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Barış Uşaklı
  • 13,440
  • 7
  • 40
  • 66
0

Just use let instead of var, i.e.:

for(let i = 0; i < docs.length; i++){

        let URL = docs[i].url;

        request(URL, function(error, response, html){
            console.log(URL);
            //other code...
}
tav
  • 587
  • 6
  • 9