How to unsubscribe from a socket.io subscription?
Asked Answered
H

11

71

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;
Haymaker answered 23/2, 2012 at 18:10 Comment(0)
F
92

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.

Fretful answered 14/3, 2012 at 4:45 Comment(2)
That seems to work, the event disappears from SocketNamespace though I'm yet to do extensive testing on this part. Also you were right about the proxy solution, so it's updated for the humour of itHaymaker
What isn't working? cbProxy was a callback method defined by the OP.Fretful
E
32

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

Erlanger answered 15/8, 2016 at 15:57 Comment(4)
This answer needs more upvotes, as most of the other ones are incomplete and/or extremely out of date.Converted
If you are using socket.io-client npm package then this is working best, since removeAllListeners() is parameterless (so you can't specify eventName).Lantern
Neither callback of off fires, not does the off works if a callback is given.Wenonawenonah
Link to the docs here.Dorey
S
31

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".

Sunset answered 9/8, 2013 at 14:2 Comment(0)
W
5

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.

Warble answered 15/10, 2013 at 14:59 Comment(0)
P
3

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;
    };
Phrygia answered 4/2, 2013 at 22:50 Comment(0)
C
2

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);
  }
Counterwork answered 8/3, 2017 at 1:40 Comment(0)
S
1

Removing an event listener on the client

var Socket = io.connect();
Socket.removeListener('test', test);
Saval answered 29/12, 2015 at 10:45 Comment(0)
S
1

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);
Sandberg answered 8/8, 2016 at 12:2 Comment(0)
R
1

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")
Readytowear answered 7/4, 2019 at 8:54 Comment(0)
D
0

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);
});
Defecate answered 4/8, 2016 at 2:20 Comment(0)
S
0

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.

Significance answered 11/9, 2019 at 11:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.