KM529 KM529 - 2 months ago 7
Node.js Question

Why can't Node see an element in an array when it is chosen by variable?

In a script I am writing, I am iterating over some messages in a mailbox and attempting to delete ones that have been read. The issue I have been getting though, is that as i iterate over the array, Node says that it cannot find the property id at the given element(which is necessary to delete or view anything). This would be fine if that property didn't exist, but when using util.inspect and specifying 1 instead of the iterator variable, it can find and print that field. The code as it exists is shown below:

for(var b = 1; b < length; b++){
setTimeout(function(){
console.log(util.inspect(messageList, false, null));
console.log(util.inspect(messageList[1], false, null));
console.log(util.inspect(messageList[1].id, false, null));
console.log(util.inspect(messageList[b].id, false, null));
var delID = messageList[b].id;
//console.log(util.inspect(messageList, false, null));
if(delID){
gmail.users.messages.trash({
auth:auth,
userId: 'me',
id: delID,
}, function(err2, delObject){
if(err2){
console.log("The message with ID: "+ delID + " could not be deleted");
console.log(err2);
return;
}
else{
console.log(util.inspect(delObject, false, null));
console.log("Message was deleted with id: " + delID);
}
})
}
}, 2000);
}


The lines above where messageList has an actual number instead of a variable shows '157550983b6c1cb' (the apostrophes print out as well). On the line where I switch it with b, it throws:

TypeError: Cannot read property of undefined
at Timeout.oneTimeout(.......etc)
at tryOnTimeout(timers.js:232:11)
at Time.listonTimeout(timers.js:202:5)


Does this mean that it is impossible to use the variable b inside the Timeout function? If so, how do I get around needing to iterate over the entire messageList with a break in between deletions?

Answer

because setTimeout is asynchronous, the callback wont get called until the for loop has finished - at this point, b will be messageList.length - so too big

You want to wait 2 seconds between each iteration - one way to do this is with a recursive function call like this

var length = messageList.length;
function myFunction(b) {
    setTimeout(function() {
        console.log(util.inspect(messageList, false, null));
        console.log(util.inspect(messageList[1], false, null));
        console.log(util.inspect(messageList[1].id, false, null));
        console.log(util.inspect(messageList[b].id, false, null));
        var delID = messageList[b].id;
        //console.log(util.inspect(messageList, false, null));
        if (delID) {
            gmail.users.messages.trash({
                auth: auth,
                userId: 'me',
                id: delID,
            }, function(err2, delObject) {
                if (err2) {
                    console.log("The message with ID: " + delID + " could not be deleted");
                    console.log(err2);
                    return;
                } else {
                    console.log(util.inspect(delObject, false, null));
                    console.log("Message was deleted with id: " + delID);
                }
            })
        }
        b += 1;
        if (b < length) {
            myFunction(b);
        }
    }, 2000);
}
if (messageList.length > 0) {
    myFunction(0);
}
// note - any code here will run immediately! 
// it wont wait for the "iteration" to complete - 
// if you need to wait for the above to finish, 
// that's a whole other set of problems

as per comments, if you want the first iteration to run immediately, change the code to

var length = messageList.length;
function myFunction(b) {
    console.log(util.inspect(messageList, false, null));
    console.log(util.inspect(messageList[1], false, null));
    console.log(util.inspect(messageList[1].id, false, null));
    console.log(util.inspect(messageList[b].id, false, null));
    var delID = messageList[b].id;
    //console.log(util.inspect(messageList, false, null));
    if (delID) {
        gmail.users.messages.trash({
            auth: auth,
            userId: 'me',
            id: delID,
        }, function(err2, delObject) {
            if (err2) {
                console.log("The message with ID: " + delID + " could not be deleted");
                console.log(err2);
                return;
            } else {
                console.log(util.inspect(delObject, false, null));
                console.log("Message was deleted with id: " + delID);
            }
        })
    }
    if (b < length - 1) {
        setTimeout(myFunction, 2000, b + 1);
    }
}
if (messageList.length > 0) {
    myFunction(0);
}
// note - any code here will run immediately! 
// it wont wait for the "iteration" to complete - 
// if you need to wait for the above to finish, 
// that's a whole other set of problems

If you want to have code that runs on completion of the iteration, things can be made a whole lot simpler with Promises - you could use this method regardless if you have code that runs on completion or not

messageList
.slice(1) // skip the first item in the array as per question code - remove this line to start at 0
.reduce(function(prom, messageItem, index) {
    return prom
    .then(function () {
        if (index) { // this avoids the 2 second wait on the first item
            return new Promise(function(resolve) {
                setTimeout(resolve, 2000);
            });
        }
    })
    .then(function() {
        console.log(util.inspect(messageList, false, null));
        console.log(util.inspect(messageItem, false, null));
        console.log(util.inspect(messageItem.id, false, null));
        var delID = messageItem.id;
        //console.log(util.inspect(messageList, false, null));
        if (delID) {
            gmail.users.messages.trash({
                auth: auth,
                userId: 'me',
                id: delID,
            }, function(err2, delObject) {
                if (err2) {
                    console.log("The message with ID: " + delID + " could not be deleted");
                    console.log(err2);
                    return;
                } else {
                    console.log(util.inspect(delObject, false, null));
                    console.log("Message was deleted with id: " + delID);
                }
            });
        }
    });
}, Promise.resolve()) // this starts the promise chain
.then(function() {
    // this code gets called once the "loop" finishes
});
// code here does not wait for the "loop" to finish
// in fact it runs before the first messageList item is even processed

This code does NOT wait for gmail.users.messages.trash to complete before starting the wait for the next iteration - if you need to wait for that, then, again, that's a whole other problem, but very very easily handled when using the promise method