Node.js + AngularJS + socket.io : connect state and manually disconnect

标签 node.js angularjs socket.io

我在客户端使用带有 socket.io 和 angularjs 的 nodejs。我从 Internet 上获取了 angular-socketio 示例并为其添加了 disconnect 方法。

套接字服务:

angular.module('app')
  .factory('socket', ['$rootScope', function ($rootScope) {

    var socket = io.connect();

    return {
      on: function (eventName, callback) {
        socket.on(eventName, function () {  
          var args = arguments;
          $rootScope.$apply(function () {
            callback.apply(socket, args);
          });
        });
      },
      emit: function (eventName, data, callback) {
        socket.emit(eventName, data, function () {
          var args = arguments;
          $rootScope.$apply(function () {
            if (callback) {
              callback.apply(socket, args);
            }
          });
        })
      },
      disconnect: function () {
        socket.disconnect();
      },
      socket: socket
    };

  }]);

Controller :

angular.module('app')
  .controller('Controller', ['$scope', 'socket', function ($scope, socket) {

    socket.emit('register')

    socket.on('connect', function () {
        console.log('Socket connected');
    });

    socket.on('disconnect', function () {
        console.log('Socket disconnected');
    });

    socket.on('register', function (reginfo) {
        console.log('Register: %s, cname=%s', reginfo.ok, reginfo.cname);
        socket.disconnect(); // <-- this line throw Error
    });

    socket.on('last', updateSnapshot);

    socket.on('state', updateSnapshot);

    function updateSnapshot(snapshot) { ... }

}]);

但是当我尝试使用这种方法断开连接时,我发现了错误:

Error: $apply already in progress
  at Error (<anonymous>)
  at beginPhase (http://localhost:4000/scripts/vendor/angular.js:8182:15)
  at Object.$get.Scope.$apply (http://localhost:4000/scripts/vendor/angular.js:7984:11)
  at SocketNamespace.on (http://localhost:4000/scripts/services/socket.js:10:32)
  at SocketNamespace.EventEmitter.emit [as $emit] (http://localhost:4000/socket.io/socket.io.js:633:15)
  at Socket.publish (http://localhost:4000/socket.io/socket.io.js:1593:19)
  at Socket.onDisconnect (http://localhost:4000/socket.io/socket.io.js:1970:14)
  at Socket.disconnect (http://localhost:4000/socket.io/socket.io.js:1836:12)
  at SocketNamespace.<anonymous> (http://localhost:4000/scripts/controllers/controller.js:38:34)
  at on (http://localhost:4000/scripts/services/socket.js:11:34)

而且我不知道去哪里挖掘......

最佳答案

[更新]

$$phase 是 Angular 的一个内部私有(private)变量,因此你不应该真的依赖它来做这样的事情。 Igor 在另一个答案中描述了一些应该改用的处理此问题的建议(我听说他对 Angular 了解一两件事。;)


当模型发生变化并且事件从 Angular 框架内触发时,Angular 可以根据需要进行脏跟踪并更新任何必要的 View 。当你想在 Angular 的 之外与代码进行交互时,你必须将必要的函数调用包装在作用域的 $apply 方法中,这样 Angular 才能知道发生了什么。这就是代码读取的原因

$rootScope.$apply(function () {
  callback.apply(socket, args);
});

等等。它告诉 Angular,“获取这段通常不会触发 Angular View 更新的代码,并按应有的方式对待它。”

问题是当您已经在 $apply 调用中调用 $apply 时。例如,以下将抛出一个 $apply already in progress 错误:

$rootScope.$apply(function() {
  $rootScope.$apply(function() {
    // some stuff
  });
});

根据您的堆栈跟踪,似乎某些对 emit 的调用(已经使用了 $apply)触发了对 on 的调用(它还使用 $apply)。要解决此问题,我们只需要在 $apply 尚未进行时调用 $apply。值得庆幸的是,作用域上有一个名为 $$phase 的属性可以告诉我们是否正在进行脏检查。

我们可以很容易地构建一个函数,它需要一个作用域和一个要运行的函数,然后仅当函数尚未运行时才使用 $apply 运行该函数:

var safeApply = function(scope, fn) {
  if (scope.$$phase) {
    fn(); // digest already in progress, just run the function
  } else {
    scope.$apply(fn); // no digest in progress, run the function with $apply
  }
};

现在我们可以替换调用

$rootScope.$apply(function...);

safeApply($rootScope, function...);

例如修改你上面的代码,

angular.module('app')
  .factory('socket', ['$rootScope', function ($rootScope) {

    var safeApply = function(scope, fn) {
      if (scope.$$phase) {
        fn(); // digest already in progress, just run the function
      } else {
        scope.$apply(fn); // no digest in progress, run with $apply
      }
    };

    var socket = io.connect();

    return {
      on: function (eventName, callback) {
        socket.on(eventName, function () {  
          var args = arguments;
          safeApply($rootScope, function () {
            callback.apply(socket, args);
          });
        });
      },
      emit: function (eventName, data, callback) {
        socket.emit(eventName, data, function () {
          var args = arguments;
          safeApply($rootScope, function () {
            if (callback) {
              callback.apply(socket, args);
            }
          });
        })
      },
      disconnect: function () {
        socket.disconnect();
      },
      socket: socket
    };

  }]);

关于Node.js + AngularJS + socket.io : connect state and manually disconnect,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14700865/

相关文章:

node.js - 使用 Passport.js 进行身份验证 通过 Nginx 提供声明内容

javascript - 保持 Node 服务器运行 "watching"以便发生事情

javascript - 将对象添加到 Firebase 时设置自己的 key - AngularFire

javascript - Socket.io:同一图像未连续发送两次

javascript - Node 和/或套接字 io 多人打地鼠

node.js - 获取 Socket.io 客户端的连接状态

javascript - 如何使用 next.js 加载 CSS 模块

javascript - onCall firebase 函数上 firebase V9 中的 CORS 策略错误

javascript - 使用 angularJS 获取数据

javascript - 带有参数和成功回调的 Angular 服务工厂