1

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.

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you. Actually, roughly I thought the reason could be this, but I thought maybe Javascript engine is smart to deal with this for developers.
1

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);
    }
}

1 Comment

Thank you. You step-by-step explanation is very clear!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.