5

クライアントでsocket.ioとangularjsでnodejsを使用しています。インターネットから 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)

どこを掘ればいいのかわからない…</p>

4

2 に答える 2

8

[アップデート]

$$phaseは Angular の内部のプライベート変数であるため、このようなことを実際に依存するべきではありません。Igor は、別の回答で、これを処理するためのいくつかの提案を説明しています。


モデルが変更され、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
    };

  }]);
于 2013-02-05T06:46:51.507 に答える
4

この問題の核心 (他のほとんどの場合と同様) は、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
    };

  }]);
于 2013-06-13T16:51:39.210 に答える