我在客户端上使用带有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)
我不明白在哪里挖......
答案 0 :(得分:8)
<强> [更新] 强>
$$phase
是Angular的内部私有变量,因此你不应该真的依赖它来做这样的事情。 Igor在另一个答案中描述了一些处理这个问题的建议,应该使用它(我听说他对Angular知道一两件事。)
当模型发生变化并且事件从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
};
}]);
答案 1 :(得分:4)
这个问题的核心(就像大多数其他情况一样)是on
方法在大多数情况下异步调用(好!),但在某些情况下同步调用(坏!)
当您从应用程序(位于“角度上下文”中的控制器内)中调用socket.disconnect()
时,它会同步触发disconnect事件,然后传播到旨在打开的on
方法中角度背景的边界。但是因为你已经处于角度背景中,棱角分明会抱怨你提到的错误。
由于此问题特定于断开连接调用,因此此处的最佳选项是
示例代码:
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
};
}]);