Elliott Elliott - 3 months ago 25
Node.js Question

JavaScript bind vs anonymous function

This code from mongoose-deep-populate

async.parallel([
User.create.bind(User, {_id: 1, manager: 2, mainPage: 1}),
Comment.create.bind(Comment, {_id: 3, user: 1}),
...
], cb)


uses
Function.prototype.bind
to ensure that
this
is bound to the proper object when the callback function
User.create
is executed in a different context. Is this equivalent to

async.parallel([
function() { User.create({_id: 1, manager: 2, mainPage: 1}) },
function() { Comment.create({_id: 3, user: 1}) },
], cb)


?

If so, in what situation is
bind
a more preferable syntax compared to using an anonymous function?

Answer

The two are quite different in a way that isn't clear in your example because you are using constant values, but consider the following:

function mul(num, times) {
    return num * times;
}

function fn1() {
    let num = 3;

    let cb = function(times) {
        return mul(num, times);
    }

    num = 5;
    console.log(`num is now: ${ num }`);

    return cb;
}

function fn2() {
    let num = 3;

    let cb = mul.bind(null, num);

    num = 5;
    console.log(`num is now: ${ num }`);

    return cb;
}

When you run the two you'll get different results:

let a1 = fn1()(5); // a1 === 25
let a2 = fn2()(5); // s2 === 15

The difference between the two is that when using bind you bind the current values to the function (as parameters) while when using an anonymous function the used values will be the ones that exist when the function is invoked.

In some scenarios you might even face an undefined when the function is executed:

var a = ["zero", "one", "two", "three", "four", "five"];
function fn(value, index) {
    console.log(value, index);
}

// doesn't work as i is undefined when the function is invoked
for (var i = 0; i < a.length; i++) {
    setTimeout(() => {
        fn(a[i], i);
    }, 45);
}

// works because the value of i and the value of a[i] are bound
for (var i = 0; i < a.length; i++) {
    setTimeout(fn.bind(null, a[i], i), 45);
}

(the example with the anonymous function will work if you use let instead of var)

The same happens when you want to pass a value which is a result of calling another function:

let counter = {
    _current: 0,
    get: function() {
        return this._current++;
    }
}

let map = {
    _items: Object.create(null),
    set: function(key, value, index) {
        this._items[key] = {
            index: index,
            value: value
        }
    }
}

// using anonymous functions the index in most cases won't reflect the real order
setTimeout(function() {
    map.set("one", 1, counter.get());
}, Math.floor(Math.random() * 1500) + 100);
setTimeout(function() {
    map.set("two", 2, counter.get());
}, Math.floor(Math.random() * 1500) + 100);
setTimeout(function() {
    map.set("three", 3, counter.get());
}, Math.floor(Math.random() * 1500) + 100);

// using bind, the index will always be correct
setTimeout(map.set.bind(map, "one", 1, counter.get()), Math.floor(Math.random() * 1500) + 100);
setTimeout(map.set.bind(map, "two", 2, counter.get()), Math.floor(Math.random() * 1500) + 100);
setTimeout(map.set.bind(map, "three", 3, counter.get()), Math.floor(Math.random() * 1500) + 100);

The reason it works differently is that when binding the counter.get() gets evaluated before the bind function is invoked so the correct return value is bound.
When using an anonymous function the counter.get() is evaluated only when the function is executed, and the order of which anonymous function is called isn't known.

Comments