Reputation: 505
I have a question about the output of the following code.
var _list = [{id:0}, {id:1}, {id:2}, {id:3}, {id:4}];
function storeList() {
for (var i = 0, j = _list.length; i < j; i++) {
var key = makeKey(_list[i].id);
_db.setValue(
function() {
console.log("OK: store a value of " + key);
},
function() {
throw "ERR: can't store a value of " + key;
},
databaseName,
key,
_list[i]);
}
}
storeList();
I expect it should output:
OK: store a value of 0
OK: store a value of 1
OK: store a value of 2
OK: store a value of 3
OK: store a value of 4
However, it outputs:
OK: store a value of 4
OK: store a value of 4
OK: store a value of 4
OK: store a value of 4
OK: store a value of 4
Why? and, what is the correct way to output? I run this javascript code on Android Webview.
Thanks in advance.
Upvotes: 1
Views: 1507
Reputation: 30557
You need to make a closure. You can do this by wrapping the code after key creation in a self executing function (IIFE)
(function(key) {
db.setValue(
function() {
console.log("OK: store a value of " + key);
},
function() {
throw "ERR: can't store a value of " + key;
},
databaseName,
key,
_list[i]
);
})(key);
This is because the scope of key
before was visible to all your iterated setValue
calls. By using an IIFE
, your passed keys scope is 'within' each setValue
call.
Upvotes: 3
Reputation: 5221
db.setValue
is an async method. So when you call it, it detaches itself from main program flow while maintaining its access to loop counter i
. Javascript always completes the current flow before executing any async code. So your loop runs 4 times, each time creating a block of async code that can only run after the currently executing program (for loop) is completed.
Note that each async block has the access to same i
. Therefore, when it's time for them to execute, all they see is the value of i which exists after the main flow was completed (which in this case is 4).
With that in mind, the easiest way to deal with these problems is to create a closure. Basically, you'll give each async block a copy of i which will remain at the value it was when the block was created (unless your async block changes it). You can do that by iife as @AmmarCSE described. A cleaner way to approach is to move this stuff out in a method.
function setValue (i) {
var key = makeKey(_list[i].id);
_db.setValue(
function() {
console.log("OK: store a value of " + key);
},
function() {
throw "ERR: can't store a value of " + key;
},
databaseName,
key,
_list[i]
);
};
function storeList() {
for (var i = 0, j = _list.length; i < j; i++) {
setValue(i);
}
}
Upvotes: 1