1

Fairly new to node.js and having issues understanding some concepts.

In the following code, line console.log('Item ' + this.itemNo + ' is done.'); displays undefined.

What could be the possible reason that this.itemNo is not accessible inside setTimeout?

async = require("async");

function doSomethingOnceAllAreDone(){
    console.log("Everything is done.");
}

function Item(itemNo, delay){
    this.delay = delay;
    this.itemNo = itemNo;

    console.log(`Created item ${itemNo} with ${delay} seconds`)
}

Item.prototype.someAsyncCall = function(callback){
    setTimeout(function(){
        console.log('Item ' + this.itemNo + ' is done.');
        if(typeof callback === "function") callback();
    }, this.delay);
};

// Create some items
let items = [];
for (let i = 0; i < 20; i++) {
    items.push(new Item(i, Math.random() * 3000));
}

// Loop though items and create tasks
var asyncTasks = [];
items.forEach(function(item){
  asyncTasks.push(function(callback){
    item.someAsyncCall(function(){
      callback();
    });
  });
});

// Add an extra task 
asyncTasks.push(function(callback){
  setTimeout(function(){
    console.log("Additional item is done.");
    callback();
  }, 3000);
});

// Execute the tasks in parallel and notify once done
async.parallel(asyncTasks, function(){
  doSomethingOnceAllAreDone();
});
Nishant Ghodke
  • 923
  • 1
  • 12
  • 21
  • 1
    Possible duplicate of [How to access the correct \`this\` inside a callback?](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback) – Estus Flask Oct 11 '18 at 12:51

2 Answers2

3

Scope in Javascript is very important if there are nested functions.

(Keep an eye on comments in the below code)

Item.prototype.someAsyncCall = function(callback) { // <-- Item object/class is accessible in lexical *this*
    var itemNo = this.itemNo; // Accessible

    setTimeout(function() { // <-- Here, we have a closure with its own scope and ITS OWN lexical *this*
        var itemNoDuplicate = this.itemNo; // Not accessible as we are inside `setTimeout` scope
        if(typeof callback === "function") callback();
    }, this.delay);
};

There are a couple of solutions to your problem:

Make use of ES6 pattern with arrow function:

Item.prototype.someAsyncCall = function(callback) {
  // Arrow function in ES6 preserves lexical *this* of the closest parent
  setTimeout(() => {
    if(typeof callback === "function") callback();
  }, this.delay);
};

Make use of bind (if you want to stick with ES5)

Item.prototype.someAsyncCall = function(callback) {
  setTimeout(function() {
    var itemNo = this.itemNo; // Yeaah! Its accessible    

    if(typeof callback === "function") callback();
  }.bind(this), this.delay);
// ^^^^^^^^^^^ -- We have bind the closest parents scope to this closure, // and now its accessible inside it 
};

Please refer to Atishay Jain's answer in this same thread to know another way to do it.

Nishant Ghodke
  • 923
  • 1
  • 12
  • 21
0

Code updated. Use this

Item.prototype.someAsyncCall = function(callback){
    var _this = this;
    setTimeout(function(){
        console.log('Item ' + _this.itemNo + ' is done.');
        if(typeof callback === "function") callback();
    }, this.delay);
};

or use this

Item.prototype.someAsyncCall = function(callback){
    setTimeout(() => {
        console.log('Item ' + this.itemNo + ' is done.');
        if(typeof callback === "function") callback();
    }, this.delay);
};
Atishay Jain
  • 1,425
  • 12
  • 22
  • for more information on `this` keyword read this https://toddmotto.com/understanding-the-this-keyword-in-javascript/ – Atishay Jain Oct 11 '18 at 11:11