【问题标题】:Node.js + AngularJS + socket.io: connect state and manually disconnectNode.js + AngularJS + socket.io:连接状态和手动断开连接
【发布时间】:2013-02-05 05:38:40
【问题描述】:

我在客户端使用 nodejs 和 socket.io 和 angularjs。我从网上找了一个 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
    };

  }]);

控制器:

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)

而且我不知道在哪里挖……

【问题讨论】:

    标签: node.js angularjs socket.io


    【解决方案1】:

    [更新]

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


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

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

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

    问题是当您已经在$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
        };
    
      }]);
    

    【讨论】:

    • 谢谢布兰登!看起来像一个黑客,但它的工作原理。你怎么看,这个检查应该在角核中吗?
    • 核中有一个检查——但是它抛出了一个错误!我认为“安全应用”不在核心中的原因是通常没有很好的理由嵌套$applys——拥有它们通常表明你有错误某处。但是,这种情况有点独特,因为您包装了一个默认情况下不与 Angular 集成的第三方库。
    • 有很多很酷的库在默认情况下没有集成,还有一大堆问题如何与 Angular 一起使用。
    • 我知道这是对问题的一种简化,但在许多情况下,包装第三方库的一种好方法是将它们放入指令中并将它们的回调包装在 $apply 中。例如,查看与我为另一个 SO 问题编写的 jQuery 颜色选择器的集成:jsfiddle.net/BinaryMuse/x2uwQ
    • 快速更新:$$phase 是 Angular 的内部私有变量,因此您不应该真正依赖它来处理此类事情。伊戈尔在另一个答案中描述了一些处理这个问题的建议,应该改为使用。
    【解决方案2】:

    这个问题的核心(就像在大多数其他情况下一样)是on 方法在大多数情况下被异步调用(好!)但在某些情况下也被同步调用(坏!)。

    当您从应用程序(从位于“角度上下文”中的控制器内)调用socket.disconnect() 时,它会同步触发断开事件,然后传播到on 方法,该方法旨在打开边界到角度上下文。但是由于您已经在 Angular 上下文中,因此 Angular 会抱怨您提到的错误。

    由于此问题特定于断开呼叫,因此此处的最佳选择是

    • 使用 setTimeout 或 $timeout 异步断开连接(将 invokeApply 参数设置为 false),或者
    • 保留一个内部标志,告诉您是否处于断开连接阶段,在这种情况下,跳过 $apply

    示例代码:

    angular.module('app')
      .factory('socket', ['$rootScope', function ($rootScope, $timeout) {
    
        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 () {
            $timeout(socket.disconnect, 0, false);
          },
          socket: socket
        };
    
      }]);
    

    angular.module('app')
      .factory('socket', ['$rootScope', function ($rootScope) {
    
        var socket = io.connect(),
            disconnecting = false;
    
        return {
          on: function (eventName, callback) {
            socket.on(eventName, function () {  
              var args = arguments;
              if (!disconnecting) {
                $rootScope.$apply(function () {
                  callback.apply(socket, args);
                });
              } else {
                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 () {
            disconnecting = true;
            socket.disconnect();
          },
          socket: socket
        };
    
      }]);
    

    【讨论】:

      猜你喜欢
      • 2012-05-28
      • 2012-10-12
      • 2017-10-06
      • 2012-11-09
      • 2011-06-30
      • 2011-05-24
      • 2018-03-03
      • 2016-07-08
      • 1970-01-01
      相关资源
      最近更新 更多