How to unsubscribe from a socket.io subscription?

Javascriptsocket.ioPublish Subscribe

Javascript Problem Overview


Suppose there are objects making subscriptions to a socket server like so:

socket.on('news', obj.socketEvent)

These objects have a short life span and are frequently created, generating many subscriptions. This seems like a memory leak and an error prone situation which would intuitively be prevented this way:

socket.off('news', obj.socketEvent)

before the object is deleted, but alas, there isn't an off method in the socket. Is there another method meant for this?

Edit: having found no answer I'm assigning a blank method to overwrite the wrapper method for the original event handler, an example follows.

var _blank = function(){};

var cbProxy = function(){
    obj.socketEvent.apply(obj, arguments)
};
var cbProxyProxy = function(){
    cbProxy.apply ({}, arguments)
}
socket.on('news', cbProxyProxy);

// ...and to unsubscribe 
cbProxy = _blank;

Javascript Solutions


Solution 1 - Javascript

From looking at the source of socket.io.js (couldn't find it in documentation anywhere), I found these two functions:

removeListener = function(name, fn)
removeAllListeners = function(name)

I used removeAllListeners successfully in my app; you should be able to choose from these:

socket.removeListener("news", cbProxy);
socket.removeAllListeners("news");

Also, I don't think your solution of cbProxy = _blank would actually work; that would only affect the cbProxy variable, not any actual socket.io event.

Solution 2 - Javascript

If you want to create listeners that "listens" only once use socket.once('news',func). Socket.io automatically will distroy the listener after the event happened - it's called "volatile listener".

Solution 3 - Javascript

Looking at the code of current version of Socket.io Client (1.4.8) it seems that off, removeAllListeners, removeEventListener are all pointing to the same function.

Calling any of those, providing event name and/or callback, gives the desired result. Not providing anything at all seems to reset everything.

Please do be cautious about the fn/callback argument. It has to be the same instance used in the code.

Example:

var eventCallback = function(data) {
  // do something nice
};
socket.off('eventName', eventCallback);

Would work as expected.

Example (will also work):

function eventCallback(data) {
  // do something nice
}
socket.off('eventName', eventCallback);

Please be cautious that the callback you are trying to remove is the one that you passed in (this one can bring a lot of confusion and frustration). This example implements a wrapper around initial callback, trying to remove that would not work as the real callback being added is an undisclosed closure instance: http://www.html5rocks.com/en/tutorials/frameworks/angular-websockets/

Here is the link to that specific line in the codebase: https://github.com/socketio/socket.io-client/blob/master/socket.io.js#L1597

Solution 4 - Javascript

Socket.io version 0.9.16 implements removeListener but not off.

You can use removeListener instead of off when unsubscribing, or simply implement off as follows:

  var socket = io.connect(url);
  socket.off = socket.removeListener;

If you are using the Backbone listenTo event subscription approach, you'll need to implement the above as Backbone calls off when unsubscribing events.

Solution 5 - Javascript

I found that in socket.io 0.9.11 and Chrome24 socket.io removeListener doesn't work.

this modified version works for me:

EventEmitter.prototype.removeListener = function (name, fn) {
        if (this.$events && this.$events[name]) {
            var list = this.$events[name];

            if (io.util.isArray(list)) {
                var pos = -1;

                for (var i = 0, l = list.length; i < l; i++) {
                    if (list[i].toString() === fn.toString() || (list[i].listener && list[i].listener === fn)) {
                        pos = i;
                        break;
                    }
                }

                if (pos < 0) {
                    return this;
                }

                list.splice(pos, 1);

                if (!list.length) {
                    delete this.$events[name];
                }
            } else  {
                    if (list.toString() === fn.toString() || (list.listener && list.listener === fn)) {
                 
                       delete this.$events[name];
                    }
            }
        }

        return this;
    };

Solution 6 - Javascript

Since I had a spot of troubles making this work figured I'd chime in here as well, along with a nice updated answer for 2017. Thanks to @Pjotr for pointing out that it has to be the same callback instance.

Example with Angular2 TypeScript in a socket-io.subscriber service. Note the "newCallback" wrapper

  private subscriptions: Array<{
    key: string,
    callback: Function
  }>;

  constructor() {
    this.subscriptions = [];
  }

  subscribe(key: string, callback: Function) {
    let newCallback = (response) => callback(response);
    this.socket.on(key, newCallback);
    return this.subscriptions.push({key: key, callback: newCallback}) - 1;
  }

  unsubscribe(i: number) {
    this.socket.removeListener(this.subscriptions[i].key, this.subscriptions[i].callback);
  }

Solution 7 - Javascript

Removing an event listener on the client

var Socket = io.connect();
Socket.removeListener('test', test);

Solution 8 - Javascript

Pre-store the events using an array, and by the time you need to unsubscribe them, use the off method, which is a built in method from socket.io:

// init
var events = []

// store
events.push("eventName")
// subscribe
socket.on("eventName", cb)

// remove
events = events.filter(event => event!="eventName")
// unsubscribe
socket.off("eventName")

Solution 9 - Javascript

To add to @Andrew Magee, here is an example of unsubscribing socket.io events in Angular JS, and of course works with Vanilla JS:

function handleCarStarted ( data ) { // Do stuff }
function handleCarStopped ( data ) { // Do stuff }

Listen for events:

var io = $window.io(); // Probably put this in a factory, not controller instantiation
io.on('car.started', handleCarStarted);
io.on('car.stopped', handleCarStopped);


$scope.$on('$destroy', function () {
    io.removeListener('car.started', handleCarStarted);
    io.removeListener('car.stopped', handleCarStopped);
});

Solution 10 - Javascript

Also on java client, it can be done the same way with the Javascript client. I've pasted from socket.io.

// remove all listeners of the connect event
socket.off(Socket.EVENT_CONNECT);

listener = new Emitter.Listener() { ... };
socket.on(Socket.EVENT_CONNECT, listener);
// remove the specified listener
socket.off(Socket.EVENT_CONNECT, listener);

Solution 11 - Javascript

This has helped me in both Angular 8 and React 16.8:

receiveMessage() {
    let newCallback = (data) => {            
        this.eventEmitter.emit('add-message-response', data);
    };
    this.socket.on('add-message-response', newCallback);

    this.subscriptions.push({key: 'add-message-response', callback: newCallback});
}

receiveMessageRemoveSocketListener() {
    this.findAndRemoveSocketEventListener('add-message-response');
}

findAndRemoveSocketEventListener (eventKey) {
    let foundListener = this.subscriptions.find( (subscription) => subscription.key === eventKey );
    
    if(!foundListener) {
      return;
    } 

    this.socket.removeListener(foundListener.key, foundListener.callback);
    this.subscriptions = this.subscriptions.filter( (subscription) => subscription.key !== eventKey );
}

Reason for using an Array of Subscriptions is that when you Subscribe to an event multiple times and you don't remove an unsubscribed subscription from the Subscription list you will most probably be right at first time you remove the subscription from the list, but later subscriptions will not be removed as you will be finding first instance only every time you unsubscribe the event.

You can simply call receiveMessage(); to subscribe to an the event and receiveMessageRemoveSocketListener(); to Unsubscribe.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBijou TrouvailleView Question on Stackoverflow
Solution 1 - JavascriptAndrew MageeView Answer on Stackoverflow
Solution 2 - JavascriptAndrás EndreView Answer on Stackoverflow
Solution 3 - JavascriptPjotrView Answer on Stackoverflow
Solution 4 - JavascriptdeedwView Answer on Stackoverflow
Solution 5 - JavascriptradiofrequencyView Answer on Stackoverflow
Solution 6 - JavascriptJoshua OhanaView Answer on Stackoverflow
Solution 7 - JavascriptJijo PauloseView Answer on Stackoverflow
Solution 8 - JavascriptRaz BuchnikView Answer on Stackoverflow
Solution 9 - Javascriptloonison101View Answer on Stackoverflow
Solution 10 - JavascriptefkanView Answer on Stackoverflow
Solution 11 - JavascriptTofiq QuadriView Answer on Stackoverflow