2013-02-05 116 views
5

我在客户端上使用带有socket.io和angularjs的nodejs。我从互联网上找到了angular-socketio的例子,并在其中添加了disconnect方法。Node.js + AngularJS + socket.io:连接状态并手动断开连接

插槽服务:

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) 

而且我不明白的地方挖...

回答

8

$$phase是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 
    }; 

    }]); 
+0

感谢布兰登!看起来像一个黑客,但它的作品。你如何看待,如果这个检查是在有角的核心? –

+0

有一个检查核心 - 但它会引发错误!我认为,“安全应用”是不是在核心的原因是_usually_没有一个很好的理由嵌套'$ apply's - 让它们通常是你有一个错误的地方的标志。但是,这种情况更具独特性,因为您正在封装未默认与Angular集成的第三方库。 –

+0

有很多不默认和一大堆的问题,如何与角度使用它们整合酷库。 –

4

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

当您从您的应用程序调用socket.disconnect()(来自位于“角度环境”中的控制器内)时,它会同步触发断开连接事件,然后传播到旨在将边界打开到角度上下文中的方法。但是,既然你已经处于角度背景下,角色就会抱怨你提到的错误。

由于这个问题是特定于断开这里所说的最好的选择是

  • 使用的setTimeout或$超时使断开异步(与invokeApply ARG设置为false),或
  • 保持一个内部标志,将告诉你,如果你是在断开阶段,在这种情况下,跳过$申请

示例代码:

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

    }]); 
相关问题