0

I'm a rookie in node.js so I apologize if my question is immature .Basically I'm trying to iterate thorough an array of values for performing certain operation but I don't know why I'm getting the values as undefined.

Code:

for (var i = 0; i < array.length; ++i) {
    console.log(array[i]); //At this point I'm getting values without any problem
    var sQ = {
        _tag: array[i],
        _pid: data.change_caption_post_mail,
        time: data.change_caption_post_time,
    };

    db.collection('tags')
        .find(sQ)
        .count({}, function(error, numOfDocs) {
            if (error) throw err;
            console.log(array[i]);
            //But here I'm getting values as **undefined**
        });
}
Irvin Lim
  • 2,393
  • 17
  • 20
Srinivas Nahak
  • 1,846
  • 4
  • 20
  • 45
  • Have you checked what the value of `i` is? – H77 Mar 13 '18 at 11:19
  • 1
    Possible duplicate of [JavaScript closure inside loops – simple practical example](https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) – ASDFGerte Mar 13 '18 at 11:19
  • 1
    Change `var i=0` to `let i= 0`. – connexo Mar 13 '18 at 11:21
  • 1
    Possible duplicate of [Javascript. Onclick returns always the same object](https://stackoverflow.com/questions/49223885/javascript-onclick-returns-always-the-same-object) – connexo Mar 13 '18 at 11:30

3 Answers3

6

Replace

var i = 0; i < array.length; ++i

by

let i = 0; i < array.length; i++

and you're done.

for (let i = 0; i < array.length; i++) {
    console.log(array[i]); //At this point I'm getting values without any problem
    var sQ = {
        _tag: array[i],
        _pid: data.change_caption_post_mail,
        time: data.change_caption_post_time,
    };

    db.collection('tags')
        .find(sQ)
        .count({}, function(error, numOfDocs) {
            if (error) throw err;
            console.log(array[i]);
            // i will be array.length here in all your callbacks
        });
}

Cause of the problem: lack of understanding scope

Check this example to understand the problem:

var creates function scope

var funcs = []

for (var i = 0; i < 10; i++) {
  funcs.push(function() {
    console.log(i)
  })
}

funcs.forEach(function(func) {
  func()
})

While you might expect this forEach loop to result in number 0 to 9 being printed, instead you get ten times 10. The cause of this is the variable i being declared using var keyword, which creates a function scope that leads to each function in funcs holding a reference to the same i variable. At the time the forEach loop is executed, the previous for-loop has ended and i holds 10 (9++ from the last iteration).

Compare how ES6's let, which creates block scope instead of function scope, behaves in this regard:

let (ES6 or officially ES2015) creates block scope:

var funcs = []

for (let i = 0; i < 10; i++) {
  funcs.push(function() {
    console.log(i)
  })
}

funcs.forEach(function(func) {
  func()
})

Because let creates block scope, each iteration of the for loop has its "own" variable i.

ES5 solution using an IIFE wrapper

If you need an ES5 solution, an IIFE (immediately invoked function expression) wrapper would be the way to go:

var funcs = []

for (var i = 0; i < 10; i++) {
  funcs.push((function(value) {
    return function() {
      console.log(value)
    }
  }(i)))
}

funcs.forEach(function(func) {
  func()
})

Here, i is passed as a parameter to each function which stores its own copy value.

The same is true for for..in loops:

var funcs = [],
  obj = {
    first: "first",
    last: "last",
    always: "always"
  }
  
for (var key in obj) {
  funcs.push(function() {
    console.log(key)
  })
}

funcs.forEach(function(func) { // outputs: "always", "always", "always"
  func()
})

Again, all functions in funcs hold the reference to the same key because var key creates a function scope that lives outside of the for..in loop. And again, let produces the result you'd probably rather expect:

var funcs = [],
  obj = {
    first: "first",
    last: "last",
    always: "always"
  }
  
for (let key in obj) {
  funcs.push(function() {
    console.log(key)
  })
}

funcs.forEach(function(func) {
  func()
})

Also compare the excellent (!) book

Nicholas C. Zakas: "Understanding ES6", no starch press, p. 8-9.

from which the examples were taken.

connexo
  • 53,704
  • 14
  • 91
  • 128
  • Great explanation sir, thanks a lot for your time and answer. – Srinivas Nahak Mar 13 '18 at 11:29
  • I don't understand why you copy in a stock answer instead of marking it as duplicate or adding to the duplicate flag already posted. – ASDFGerte Mar 13 '18 at 11:29
  • I don't have enough reputation to decide on my own whether a question should be marked a duplicate. I see many questions remain active because not enough close votes. For the time being, OP has a correct and exhaustive answer right here. – connexo Mar 13 '18 at 11:32
2

Change var i = 0 to let i = 0.

for (let i = 0; i < array.length; ++i) {
console.log(array[i]); //At this point I'm getting values without any problem
var sQ = {
    _tag: array[i],
    _pid: data.change_caption_post_mail,
    time: data.change_caption_post_time,
};

db.collection('tags')
    .find(sQ)
    .count({}, function (error, numOfDocs) {
        if (error) throw err;
        console.log(array[i]);
    });

}

N.B. When the callback function (function (error, numOfDocs) {...}) is executing the for loop is finished. if we declare var i then i is equal to array.length when executing callback so, array[array.length] returns undefined. One of the solutions is to use ES6 let which will create callback's block scope i for each iteration.

Sajib Khan
  • 22,878
  • 9
  • 63
  • 73
  • I guess its not about scope..if u observe the variable value be undefined in call back function as it is async .To tackle this either u assign value to global variable or pass it in the callback result. – Anusha kurra Mar 13 '18 at 11:28
  • This answer lacks an explanantion as of why that solves the problem, and even what the cause of the problem is. – connexo Mar 13 '18 at 11:37
  • Added explanation. Thanks @connexo! – Sajib Khan Mar 13 '18 at 11:45
0

The problem is that in your callback function, the reference of i remains only one which is already changed by the time callback is called.

you can change your code to

for (var i = 0; i < array.length; ++i) {
    (function(){
    var index = i;
    console.log(array[index]); //At this point I'm getting values without any problem
    var sQ = {
        _tag: array[index],
        _pid: data.change_caption_post_mail,
        time: data.change_caption_post_time,
    };

    db.collection('tags')
        .find(sQ)
        .count({}, function(error, numOfDocs) {
            if (error) throw err;
            console.log(array[index]);
            //But here I'm getting values as **undefined**
        });
    })();
}

it should work fine with this tweak