Srinivas Nahak
Srinivas Nahak

Reputation: 1876

why I'm getting array values as "undefined"

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**
        });
}

Upvotes: 0

Views: 140

Answers (3)

Sajib Khan
Sajib Khan

Reputation: 24156

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.

Upvotes: 2

Sajal Preet Singh
Sajal Preet Singh

Reputation: 379

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

Upvotes: 0

connexo
connexo

Reputation: 56754

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.

Upvotes: 6

Related Questions