Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove anonymous JavaScript function from array/object/Set

How can Node's emitter.removeListener be implemented in ES2015? Adding a callback to an array is easy:

let callbacks = [];
function registerCallback(handler) {
    callbacks.push(handler);
});

How can that particular function be removed later, without registerCallback returning some identifier for the function? In other words, unregisterCallback(handler) should not need any other parameter, and should remove that handler. How would unregisterCallback check that an anonymous function had been previously added?

Is running handler.toString() (and potentially a hash function on that) a reliable solution to create an identifier for the function? Or how else should unregisterCallback iterate through callbacks to remove that particular element? (Or find the appropriate key in an object or function in a Set.)

mySet.add(function foo() { return 'a'})
mySet.has(function foo() { return 'a'})  // false
like image 400
Dan Dascalescu Avatar asked Nov 09 '16 08:11

Dan Dascalescu


2 Answers

The usual solution is to pass the function itself as argument to the unregisterCallback function. That's what's done in jQuery for example.

So the unregisterCallback function just has to use indexOf to find the index of the callback:

function unregisterCallback(handler) {
    var index = callbacks.indexOf(handler);
    if (~index) callbacks.splice(index, 1);
}

Of course it means the user code must keep the function, it can't be an function defined in the call to registerCallback.

This doesn't work:

registerCallback(function foo() { return 'a'});
// later...
unregisterCallback(function foo() { return 'a'}); // this is a different function

This works:

function foo(){
    return 'a'
}
registerCallback(foo);
// later...
unregisterCallback(foo); // it's the same function

You can also provide the ability to remove by name:

// pass either the function or its name
function unregisterCallback(handler) {
  var index = -1;
  if (typeof handler==="string") {
      for (var i=0; i<callbacks.length; i++) {
        if (callbacks[i].name===handler) {
          index = i;
          break;
        }
      }
  } else {
      index = callbacks.indexOf(handler);
  }
  if (~index) callbacks.splice(index, 1);
}
registerCallback(function foo() { return 'a'});
unregisterCallback("foo");

But the responsibility of name unicity is then in the user code realm, which may be OK, or not, depending on your application.

like image 105
Denys Séguret Avatar answered Oct 30 '22 01:10

Denys Séguret


You can go for a design where the event emitter returns a callable that will update internal state:

const dispose = sleep.on('sheep', ::sleep.tick)
sleep.once('baanough', dispose)
like image 30
Filip Dupanović Avatar answered Oct 30 '22 02:10

Filip Dupanović