2013-02-05 14 views
5

Ich benutze nodejs mit socket.io und angularjs auf dem Client. Ich habe eckige-socketio Beispiel aus dem Internet aufgenommen und disconnect Methode zu It hinzugefügt.Node.js + AngularJS + socket.io: verbinden Zustand und manuell trennen

Sockel Service:

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) { ... } 

}]); 

Aber wenn ich versuche, verwenden Sie diese Methode trennen ertappe ich Fehler:

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) 

Und ich verstehe nicht, wo zu graben ...

Antwort

8

$$phase ist eine interne, private Variable zu Angular, und daher sollte man sich nicht wirklich auf solche Dinge verlassen. Igor beschreibt, in eine andere Antwort, einige Vorschläge für das Handling, die stattdessen verwendet werden soll (ich höre, dass er weiß, das eine oder andere über Angular;.)


Wenn Modelle ändern und Ereignisse Feuer aus dem Winkelrahmen, Angular kann bei Bedarf eine fehlerhafte Aufzeichnung vornehmen und alle erforderlichen Ansichten aktualisieren. Wenn Sie mit Code außerhalb von von Angular interagieren möchten, müssen Sie die erforderlichen Funktionsaufrufe in der $apply Methode eines Bereichs umbrechen, so dass Angular weiß, dass etwas passiert. Deshalb lautet der Code

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

und so weiter. Es sagt Angular, "nimm diesen Code, der normalerweise keine Winkelansicht-Updates auslöst, und behandle ihn so, wie er sollte." Das Problem ist, wenn Sie $apply aufrufen, wenn Sie bereits in einem $apply Anruf sind. Zum Beispiel würde folgende einen $apply already in progress Fehler werfen:

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

Basierend auf dem Stack-Trace, es sieht aus wie ein Anruf zu emit ausgelöst einen Anruf zu on (die bereits $apply verwendet) (die $apply auch verwendet). Um dieses Problem zu beheben, müssen wir nur $apply aufrufen, wenn eine $apply nicht bereits ausgeführt wird. Zum Glück gibt es eine Eigenschaft auf dem Bereich namens $$phase, die uns sagen kann, ob ein dreckiger Scheck läuft.

Wir können leicht eine Funktion erstellen, die einen Bereich und eine Funktion auszuführen dauert, und dann läuft die Funktion mit $apply nur, wenn man nicht bereits im Gange:

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

Jetzt können wir Anrufe ersetzen

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

zu

safeApply($rootScope, function...); 

zum Beispiel um den Code zu ändern Sie haben oben,

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

Dank Brandon! Sieht aus wie ein Hack, aber es funktioniert. Wie denkst du, sollte diese Überprüfung in eckigen Kern sein? –

+0

Es gibt einen Check-in-Kern - aber es wirft einen Fehler auf! Ich denke, der Grund, dass die "sichere Anwendung" nicht im Kern ist, ist, dass es normalerweise keinen guten Grund gibt, "$ apply" zu verschachteln - sie zu haben ist normalerweise ein Zeichen dafür, dass du irgendwo einen Fehler hast. Dieser Fall ist jedoch insofern etwas einzigartiger, als Sie eine Bibliothek eines Drittanbieters einpacken, die standardmäßig nicht in Angular integriert ist. –

+0

Es gibt viele coole Bibliotheken, die standardmäßig nicht integriert sind und eine ganze Reihe von Fragen, wie man sie mit Angular verwenden kann. –

4

Der Kern des Problems in dieser (wie gerade in den meisten anderen Fällen) ist, dass die on Methode asynchron die meiste Zeit (gut!) genannt wird, sondern auch synchron in einigen Fällen (Schlecht!).

Beim socket.disconnect() aus Ihrer Anwendung aufrufen (von innerhalb einer Steuereinheit, die in der „Winkel Kontext“ lebt) er das Trennungsereignis synchron ausgelöst wird, die dann die on Verfahren ausbreitet, in der die Grenze in die Winkel Kontext öffnen ausgebildet ist. Aber da Sie sich bereits im eckigen Kontext befinden, klagt eckig über den von Ihnen erwähnten Fehler.

Da diese Frage der besten Optionen spezifisch hier zum Trennen rufen zu

  • das Trennen asynchron machen, indem Sie setTimeout oder $ timeout (mit invokeApply arg auf false gesetzt) ​​oder
  • keep ein internes Flag, die Ihnen sagen, wenn Sie in der Trennphase befinden, und in diesem Fall überspringen die $ gelten

Beispielcode:

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

    }]); 

oder

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

    }]);