Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to compare two functions that are called with `.bind()`?

Tags:

javascript

I am building a middleware layer between my Socket.IO events and the rest of my application. I do this so that I can swap out Socket.IO for something else in the future.

I store callback functions in an array. When a specific event triggers, I loop over the array and execute the callback functions. That works like a charm.

The problem lies in the removing of a callback from that array. When a callback function needs to be removed, I loop over the array and check every array item to see if it is equal (using ===) to the callback I want to remove. When just the callback is stored in the array, this works fine. However, when a callback is stored in combination with .bind(), the equal check returns false.

I created a (simplified) codepen to demonstrate the problem: http://codepen.io/petergoes/pen/wWPJdg?editors=0012.

I draw inspiration from the Socket.IO code to write my removal method: https://github.com/socketio/socket.io-client/blob/master/socket.io.js#L1623. However, there the same problem occurs.

The big question:
How can I compare two functions when (one or both of them) are called with the .bind() method?

I found this answer how do I compare 2 functions in javascript. However, comparing the string version of a function feels a bit sketchy

The codepen for reference:

var callbackList = [];

var objA = {
    myCallbackFunction: function myCallbackFunction() {
        console.log('hello my name is:', this.myName);
    }
}

var objB = {
    register: function register(callback) {
        console.log('register callback');
        callbackList.push(callback);
    },

    remove: function remove(callback) {
        console.log('remove callback');
        if(callbackList[0] === callback) {
            console.log('callback found, splice it');
            callbackList.splice(0, 1);
        } else {
            console.log('callback NOT found!');
        }
        console.log('callbackList length:', callbackList.length);
    }
}

objB.register(objA.myCallbackFunction.bind({myName: 'Peter'}));
objB.remove(objA.myCallbackFunction.bind({myName: 'Peter'}));

console.log('\nreset callbackList\n');
callbackList = [];

objB.register(objA.myCallbackFunction);
objB.remove(objA.myCallbackFunction);
like image 481
Peter Goes Avatar asked Jul 12 '16 14:07

Peter Goes


2 Answers

I think the best solution here is to store a key alongside your callback inside of the array. This is a lot more reliable and way less hacky than any solution comparing functions:

register: function register(callback, key) {
        console.log('register callback');
        var obj = {
           key: key,
           callback: callback
        }
        callbackList.push(obj);
    }

you can then call remove by passing the key desired rather than the callback, and you can compare as such:

if(callbackList[0].key === key)

Just make sure you pass the desired key when registering and access the callback property within the array objects where necessary.

I think this solution using labels is much cleaner and doesn't require any confusing or strange code.

like image 55
Pabs123 Avatar answered Oct 13 '22 01:10

Pabs123


Consider this:

function a(x) { return x; }

var b = a.bind({ foo: 'foo' });

a !== b is true because a.bind returns a new function, that is a new object, and object are compared by reference.

The hack:

Keep in mind messing up with native prototypes is not recomended, this is a nasty hack and using keys as @Pabs123 said might be a better approach

Function.prototype.bind = (function () {
    var originalBind = Function.prototype.bind;
    return function (obj) {
        var bound = originalBind.apply(this, Array.prototype.slice.call(arguments, 1));
        bound.original = this;

        return bound;
    };
}());

Now have a reference to the function that has been bound and you can check that one too when comparing:

a = function (x) { return x; };
b = a.bind({});
b.original === a; // true

Note that the hack must be implemented before any binding is done, any calls to bind before the hack executes will not produce the original property on the result function.


A simmilar approach, but without doing the hack would be to do your custom bind method that keeps the reference to the original function. But you'll need to use this method instead of the native bind in order to get the reference to the original function.

like image 32
Marco Scabbiolo Avatar answered Oct 12 '22 23:10

Marco Scabbiolo