Wayne Huang
Wayne Huang

Reputation: 505

variable set to last value in for loop in Javascript

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

Answers (2)

AmmarCSE
AmmarCSE

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

Akash
Akash

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

Related Questions