如何取消订阅socket.io?

71
假设有对象订阅套接字服务器如下所示: socket.on('news', obj.socketEvent) 这些对象寿命短暂且经常创建,生成许多订阅。这似乎是一种内存泄漏和容易出错的情况,按照直觉可以通过以下方式来防止: socket.off('news', obj.socketEvent) 在对象被删除之前执行此操作,但遗憾的是,套接字中没有一个“off”方法。是否有另一个方法用于此操作?
编辑:未找到答案,因此我将一个空方法分配给覆盖原始事件处理程序的包装方法,以下是示例。
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;
11个回答

91

通过查看socket.io.js源代码(无法在文档中找到),我发现了这两个函数:

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

我在我的应用程序中成功使用了removeAllListeners;你可以从这些中选择:

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

另外,我认为你提出的解决方案 cbProxy = _blank 实际上不会起作用;这只会影响 cbProxy 变量,而不会影响任何实际的 socket.io 事件。


看起来这个方法可行,虽然我还没有对此进行广泛的测试,但事件已经从SocketNamespace中消失了。另外,你关于代理解决方案的想法是正确的,所以它已经更新了,只是为了好玩。 - Bijou Trouvaille
1
什么没有起作用?cbProxy是由OP定义的回调方法。 - Andrew Magee

31

看当前版本的Socket.io客户端代码(1.4.8),似乎 off removeAllListeners removeEventListener 都指向同一个函数。

调用其中任意一个,提供事件名称和/或回调,即可得到预期结果。如果什么都不提供,则似乎会重置所有内容。

请谨慎处理 fn/callback 参数。它必须是代码中使用的相同实例。

例如:

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

会按预期工作。

示例(也会起作用):

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

请注意,您想要删除的回调函数是您传入的那个(这会带来很多混乱和沮丧)。 此示例实现了一个包装器来包装初始回调函数,尝试删除它将不起作用,因为实际添加的回调函数是未公开的闭包实例:http://www.html5rocks.com/en/tutorials/frameworks/angular-websockets/

这是代码库中特定行的链接:https://github.com/socketio/socket.io-client/blob/master/socket.io.js#L1597


2
这个答案需要更多的赞,因为大多数其他答案都不完整和/或过时。 - Anson Kao
如果您正在使用 socket.io-client npm 包,则此方法效果最佳,因为 removeAllListeners() 是无参数的(因此您无法指定 eventName)。 - Martin Schneider
如果给定回调函数,则 off 不起作用,也不会触发回调函数。 - Koushik Shom Choudhury
文档链接在这里(https://socket.io/docs/v3/listening-to-events/#socket-once-eventName-listener)。 - ViniciusArruda

31
如果您想创建只"监听"一次的侦听器,请使用socket.once('news',func)。Socket.io会在事件发生后自动销毁该侦听器 - 这被称为"易失性侦听器"。

5

Socket.io版本0.9.16实现了removeListener但没有实现off

在取消订阅时,可以使用removeListener代替off,或者按以下方式实现off

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

如果你使用Backbone的 "listenTo" 事件订阅方法,那么当取消事件订阅时,你需要将上述内容实现为Backbone调用 "off" 方法。

3

我发现在socket.io 0.9.11版本以及Chrome24中,socket.io的removeListener方法不起作用。

以下是经过修改后可以正常使用的版本:

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;
    };

2

因为我在让这个工作过程中遇到了一些麻烦,所以我想在这里加入我的意见,并提供一个2017年的新答案。感谢@Pjotr指出必须是相同的回调实例。

以下是使用Angular2 TypeScript在socket-io.subscriber服务中的示例。请注意“newCallback”包装器。

  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);
  }

1

同样在 java 客户端上,可以用与 Javascript 客户端相同的方式完成。我已经从 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);

1
在客户端上删除事件侦听器。
var Socket = io.connect();
Socket.removeListener('test', test);

1

使用数组预先存储事件,当你需要取消订阅时,使用socket.io的内置方法off

// init
var events = []

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

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

0

补充@Andrew Magee的话,这里有一个在Angular JS中取消订阅socket.io事件的例子,当然也适用于Vanilla JS:

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

监听事件:

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);
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接