Node.js + AngularJS + socket.io:连接状态并手动断开连接

我在客户端上使用了socket.io和angularjs的nodejs。 我从互联网上find了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) 

我不明白在哪里挖…

[更新]

$$phase是Angular的一个内部私有variables,因此您不应该依赖于这样的事情。 伊戈尔在另一个答案中描述了一些处理这个应该使用的build议(我听说他对Angular知道一两件事);


当模型在Angular框架内发生变化并触发事件时,Angular可以根据需要进行脏跟踪并更新所有必要的视图。 当你想与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 )触发了一个打电话(也使用$apply )。 要解决这个问题,我们只需要调用$apply如果$apply还没有进行。 值得庆幸的是,有一个名为$$phase的属性,可以告诉我们是否正在进行脏检查。

我们可以很容易地构build一个函数来运行一个范围和一个函数,然后用$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 } }; 

现在我们可以将呼叫replace为

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

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

从应用程序中调用socket.disconnect() (从位于“angular context”中的控制器中调用socket.disconnect() )时,会同步触发断开连接事件,然后传播到打开边界到angular度上下文的on方法。 但是既然你已经处在angular度上,angular色就会抱怨你提到的错误。

由于这个问题是特定的断开呼叫这里最好的select是

  • 通过使用setTimeout或$ timeout(使用invokeApply arg设置为false)或者使用断开asynchronous来实现断开连接
  • 保持一个内部的标志,告诉你,如果你在断开阶段,在这种情况下,跳过$申请

示例代码:

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