S. Lazuardi
S. Lazuardi

Reputation: 3

Using _.after() in async callback, polluting array variable

I hope this question is not a duplicate, I have searched similar question here but got no matching result.

In node.js Below code is polluting the storage variable. And I really can't figure out why.

var _ = require('underscore'); // same result by using lodash

function add(arr, callback) {
    var l = arr.length,
        storage = [];

    function doCallback() {
        callback(storage);
    }

    returnStorage = _.after(l, doCallback);
    _.each(arr, function (a) {
        _.delay(function () {
            storage.push(a);
            returnStorage();
        }, 10);
    });
}

function callbackHandler(result) {
    console.log(result);
}

add([1,2,3], callbackHandler),
add([4,5,6,7], callbackHandler);

// in console will print wrong result:
// [ 4 ]
// [ 4, 5 ]
// [ 4, 5, 6 ]
// [ 4, 5, 6, 7 ]

However if I don't use _.after() it will give the expected result. Using underscore or lodash give same result.

Below code is working fine.

var _ = require('underscore'); // same result by using lodash

function add(arr, callback) {
    var l = arr.length,
        storage = [];

    function returnStorage() {
        if (storage.length == l) {
            callback(storage);
        }
    }

    _.each(arr, function (a) {
        _.delay(function () {
            storage.push(a);
            returnStorage();
        }, 10);
    });
}

function callbackHandler(result) {
    console.log(result);
}

add([1,2,3], callbackHandler),
add([4,5,6,7], callbackHandler);

// in console will print correct result:
// [ 1, 2, 3 ]
// [ 4, 5, 6, 7 ]

How should i identify the root cause !

Upvotes: 0

Views: 220

Answers (1)

Cyrbil
Cyrbil

Reputation: 6478

The magic of javascript's context. When you are doing:

returnStorage = _.after(l, doCallback);

returnStorage is the same for your second call of add.

You need to declare it with var to make it new and local to the function.

var _ = require('underscore'); // same result by using lodash

function add(arr, callback) {
    var l = arr.length,
        storage = [];

    function doCallback() {
        callback(storage);
    }

    var returnStorage = _.after(l, doCallback);
    _.each(arr, function (a) {
        _.delay(function () {
            storage.push(a);
            returnStorage();
        }, 10);
    });
}

function callbackHandler(result) {
    console.log(result);
}

add([1,2,3], callbackHandler),
add([4,5,6,7], callbackHandler);

// in console:
// [ 1, 2, 3 ]
// [ 4, 5, 6, 7 ]

Bit of explanation:

On your first call of add(), returnStorage is undefined. It will then be defined it the global context since their is no var before it.

On your second call, the variable is declared, and when you set it, you also set it for the first add() call. So after the 10ms each elements of [1,2,3] have called returnStorage (the second one) and the next element of [4,5,6,7] will trigger the returnStorage callback, any further calls will also triggers it.

Upvotes: 2

Related Questions