Reputation: 13
I'm writing a Javascript function that pulls words out of a CloudDB database and puts them in one of three arrays: verb, adjective, or noun. Once all the words are in place and the last loop runs, I want to run the next function. Here's the code:
function verbnoun(array){
verbs = [];
adjectives = [];
nouns = [];
for (i = 0; i<array.length; i++){
//console.log(i); <-- returns 0,1,2,3...57,58 as expected
my_db.get(array[i], function(err, doc) {
if (!err){
if (doc.type == "verb"){
verbs.push(doc.word);
}
else if (doc.type == "adjective"){
adjectives.push(doc.word);
}
else if (doc.type == "noun"){
nouns.push(doc.word);
}
}
//console.log(i); <-- returns 59,59,59,59,59
if (i+1 == array.length){
nextFunction(verbs, adjectives, nouns);
}
});
}
}
But I can't figure out how to run the next function after the last loop. If I try to run it outside of the for loop, I just get empty arrays. Using an if statement to trigger on the last loop doesn't seem to work either, because the "i" in the for loop is stuck on 59 each time instead of counting up from 0,1,2,3,etc (see comment in code), so I can't single out which is the last loop.
This is my first post on StackExchange, so thanks for your patience.
Upvotes: 1
Views: 97
Reputation: 1074238
You're running into two (or possibly three) issues there:
The reason you can't do it after the for
loop is that the get
calls to the database haven't finished yet; they're asynchronous. In fact, it's important to remember that your function will return before the first get
returns.
i
within the callback you passed get
is an enduring reference to the variable i
, not a copy of it when the callback was created, which is why you saw it was always 59 (the array length).
Your code is falling prey to The Horror of Implicit Globals by not declaring your local variables.
See comments for how you might address it:
function verbnoun(array) {
// Use var to declare your variables
var verbs = [];
var adjectives = [];
var nouns = [];
// Use a counter to remember how many responses you've had
var responses = 0;
// Schedule the calls
for (var i = 0; i < array.length; i++) {
// Call a function do do the work, passing in the index
// for this loop
doGet(i);
}
// Remember, your function returns at this point, before ANY
// of the callbacks occurs
// This function does the work
function doGet(index) {
// Because we're using `index`, not `i`, we get a value
// for each call that won't change
my_db.get(array[index], function(err, doc) {
if (!err) {
if (doc.type == "verb") {
verbs.push(doc.word);
} else if (doc.type == "adjective") {
adjectives.push(doc.word);
} else if (doc.type == "noun") {
nouns.push(doc.word);
}
}
// Count this response
++responses;
// If we have all of them, we're done
if (responses == array.length) {
nextFunction(verbs, adjectives, nouns);
}
});
}
}
Although actually, in this particular case, we don't need index
within the callback, so we didn't absolutely need to separate the calls out into a builder function. But it still makes for a nice, clean separation of what's going on.
Upvotes: 1
Reputation: 48257
You are suffering from closure over i
, which has (probably) been incremented all the way up to 59 before any of the get
calls return and fire their callback.
Essentially, i
represents the number of requests sent, not the number that have returned successfully. You need a different counter to track that value.
function verbnoun(array) {
verbs = [];
adjectives = [];
nouns = [];
// New counter for completed requests
var finished = 0;
for (i = 0; i < array.length; i++) {
//console.log(i); <-- returns 1,2,3...57,58,59 as expected
my_db.get(array[i], function(err, doc) {
if (!err) {
if (doc.type == "verb") {
verbs.push(doc.word);
} else if (doc.type == "adjective") {
adjectives.push(doc.word);
} else if (doc.type == "noun") {
nouns.push(doc.word);
}
}
//console.log(i); <-- returns 59,59,59,59,59
if (++finished == array.length) {
nextFunction(verbs, adjectives, nouns);
}
});
}
}
Upvotes: 2