2013-02-05 115 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 
    }; 

    }]); 
相關問題