I use nodejs with socket.io and angularjs on client. I picked up angular-socketio example from the Internet and added disconnect method to It.
Socket 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) { ... }
}]);
But when I try to disconnect use this method I catch Error:
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)
And I don't understand where to dig…
[Update]
$$phase is an internal, private variable to Angular, and thus you should not really depend on it for things like this. Igor describes, in another answer, some suggestions for handling this which should be used instead (I hear he knows a thing or two about Angular. ;)
When models change and events fire from within the Angular framework, Angular can do dirty tracking as necessary and update any necessary views. When you want to interact with code outside of Angular, you have to wrap the necessary function calls in the $apply method of a scope, so that Angular knows something is happening. That's why the code reads
$rootScope.$apply(function () {
callback.apply(socket, args);
});
and so forth. It's telling Angular, "take this code that normally wouldn't trigger Angular view updates, and treat it like it should."
The problem is when you call $apply when you're already in an $apply call. For example, the following would throw an $apply already in progress error:
$rootScope.$apply(function() {
$rootScope.$apply(function() {
// some stuff
});
});
Based on your stack trace, it looks like some call to emit (which already uses $apply) triggered a call to on (which also uses $apply). To fix this problem, we need to only call $apply if an $apply is not already in progress. Thankfully, there is a property on the scope called $$phase that can tell us if a dirty check is in progress.
We can easily build a function that takes a scope and a function to run, and then runs the function with $apply only if one isn't already in progress:
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
}
};
Now we can replace calls to
$rootScope.$apply(function...);
to
safeApply($rootScope, function...);
For example, to modify the code you have above,
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
};
}]);
The core of the problem in this (just like in most of the other cases) is that the on method is called asynchronously most of the time (good!) but also synchronously in some cases (bad!).
When you call socket.disconnect() from your application (from within a controller which lives in the "angular context") it synchronously fires the disconnect event which then propagates into the on method which is designed to open the boundary into the angular context. But since you are already in the angular context, angular complains with the error you mentioned.
Since this issue is specific to the disconnect call the best options here are to
make the disconnect asynchronous by using setTimeout or $timeout (with the invokeApply arg set to false), or
keep an internal flag that will tell you if you are in the disconnect phase, and in that case, skip the $apply
Example code:
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
};
}]);
or
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
};
}]);
Related
var Async = require('async');
var Test_Hander = function () {
};
Test_Hander.prototype.begin = function () {
Async.series([
this.first, // this.first.bind(this) does not work either
this.second
],function (error) {
if (error) {
// shit
}
else {
// good
}
});
};
Test_Hander.prototype.first = function (callback) {
console.log('Enter first function');
callback(null,'I am from first function');
};
Test_Hander.prototype.second = function (one, callback) {
console.log('Enter second function');
console.log('parameter one: ');
console.log(one);
console.log(callback);
callback(null);
};
var hander = new Test_Hander();
hander.begin();
I want to pass some value from function first to function second. And I know that waterfall and global variable are both ok. However, can I just pass a result value from function first to function second without using waterfall and global variable?
Using promise you can chain the methods and execute the code similar to async.series and you don't need to require a async module.
var Test_Hander = function () {
};
Test_Hander.prototype.begin = function () {
this.first()
.then(this.second)
.catch(function(err){
// Handle error
});
};
Test_Hander.prototype.first = function (callback) {
return new Promise(function(reject, resolve){
// do something
// if err : reject(err)
// else : resolve(data);
});
};
Test_Hander.prototype.second = function (responsefromFirst) {
// Do somethign with response
};
var hander = new Test_Hander();
hander.begin();
I am using this code to create a server that listens on a specific port and ip
function notifier() {
var net = require('net');
var host = 'x.x.x.x';
var port = xxxx;
var server = net.createServer(function (socket) {
socket.on('data', function (data) {
console.log("data " + data.toString());
});
socket.on('close', function (data) {
console.log('closed');
});
socket.on('error', function (err) {
console.log(err);
});
});
function startServer() {
server.listen(port, host, function () {
console.log('started');
});
}
function stopServer() {
server.close(function () {
console.log('stopped');
});
}
startServer();
}
I have also created a startServer and stopServer function.
If I call the function notifier() then the server starts but I cant figure out how I should call the stopServer function outside the function notifier()
Maybe I have made a huge mistake here and that I cant call it elsewhere, then maybe someone could point me in the right direction how it should be done correct.
you can use 'notifier()' function as a factory method and return an interface object to communicate with the server instance, something like this...
function notifier() {
var net = require('net');
var host = 'x.x.x.x';
var port = xxxx;
var server = net.createServer(function (socket) {
// ... omitted for brevity
});
return {
startServer() {
server.listen(port, host, function () {
console.log('started');
});
},
stopServer() {
server.close(function () {
console.log('stopped');
});
}
}
}
let server = notifier();
server.startServer();
server.stopServer();
Sincé in JavaScript functions are first class you can return stopServer to call it from elsewhere.
function notifier(){
function stopServer(){
....
}
return stopServer; // note that we do not call the function, just return it
}
var x = notifier();
x(); // calls stopServer
I have the following TypeScript and nodeJS file which registers a series of socket.io namespaces. I then have some mocha tests which test those namespaces, however, my first test always fails, unless I am live reloading, in which case the second time around the tests will pass. NB: the db.Line.findAll() and similar functions are sequelize functions returning promises.
export class RealTimeStations {
public complete: Promise<boolean>;
server: any;
constructor(io: any) {
this.server = io;
this.complete = Promise.resolve(db.Line.findAll()).then(lines => {
// do some mapping to generate routes and cells
this.registerEndPoints(routes, [], cells);
}).catch(err => console.log(err))
}
registerEndPoints(routes: Array<string>, nsps: Array<any>, cells: Array<string>) {
for (let i = 0; i < routes.length; i++) {
nsps.push(this.server.of('/api/testNamespace/' + routes[i]));
let that = this;
nsp.on('connection', function (socket) {
that.emitInitialPackage(nsps[i], routes[i], cells[i]);
});
}
}
emitInitialPackage(nsp: any, name: string, cell: any) {
return db.Line.find({/* Some sequelize params*/}).then(results => {
nsp.emit('value', results);
}).catch(err => console.log(err));
}
}
I have seen this problem before, where the socketio setup wasn't being completed before the test executed, hence I moved to the promise based approach, however, this test now fails first time around but then on 2nd and subsequent livereloads, it passes...
describe('Stations Socket /api/getLine/stations', function () {
beforeEach(function (done) {
models.sequelize.sync({ force: true }).then(function () { //setup SQLite db
sequelize_fixtures.loadFile('C:/Users/George/Source/Repos/Coty%20Monitoring%20System/server/seeders/default.json', db) //seed the db
.then(function () {
app.set('port', '3000');
server = http.createServer(app);
server.listen('3000', function () { });
var io = require('socket.io').listen(server);
new RealTimeStations(io).complete.then(() => { //config socketio namespaces
socket = require('socket.io-client')('http://localhost:3000/api/testNamespace/2');
done();
});
});
})
})
describe('Setup', () => {
it('Should connect and receive correct state', function (done) {
socket.on('connect', function () {
socket.on('value', function (test) {
test.stations.should.equal('"expected value"');
done();
});
});
});
});
afterEach(function () {
socket.disconnect();
server.close();
socket = null;
})
})
Update:
The issue is that the nsp.emit('value', results); line doesn't get executed before the complete promise resolves. This means that when the test begins and connects to the server, the value event isn't triggered, and as the connect event is only sent once, the test just timeouts.
So I need a way of linking the complete promise to the emitInitialPackage promise.
I'm using Async library for asynchronous programming on NodeJS. I want to make a helper method (that also use async method), rather than put all code in same place.
Here is my sample code:
var solve = function() {
async.waterfall([
// a long working task. huh
function (callback) {
setTimeout(function() {
console.log('hello world!');
callback();
}, 2000);
},
function (callback) {
authorService.helperAsync();
},
function (callback) {
setTimeout(function() {
console.log('bye bye!');
}, 2000);
}
]);
};
solve();
In other file, I make an async function:
var helperAsync = function() {
async.waterfall([
function(callback) {
console.log('task 1');
callback();
},
function (callback) {
console.log('task 2');
callback();
}
]);
}
I want the result should be:
Hello World
Task 1
Task 2
Bye Bye
But the output is only
Hello World
Task 1
Task 2
. How can I fix my code ?
Thanks :)
You need to setup each file to be used as a module, which involves exporting the function that you'd like to return, like so:
solve.js
var async = require('async');
var helperAsync = require('./helperAsync.js');
module.exports = function solve() {
async.waterfall([
function helloOne(next) {
setTimeout(function() {
console.log('hello world!');
return next();
}, 2000);
},
function helperCall(next) {
helperAsync(params, function(){
return next();
});
},
function byeBye(next) {
setTimeout(function() {
console.log('bye bye!');
return next();
}, 2000);
}
], function(result){
return result;
});
};
helperAsync.js
var async = require('async');
module.exports = function helperAsync (params, callback) {
async.waterfall([
function(next) {
console.log('task 1');
return next();
},
function (next) {
console.log('task 2');
return next();
}
], function(result){
return callback(result);
});
};
I've renamed the callback from your async.waterfall to next in order to prevent confusion with the main callback of the module.
I am using a third party library. Which is using node domain for error handling.
If the callback function passed to that third party library have any error, It end-up in calling my callback multiple times.
Example code is:
var startFunction = function (callback) {
//Call thirdParty function and wait for response
thirdPartyFunction(function (error, data) {
console.log("Called with");
console.log(arguments);
//Assume there is an error in my callback function
setTimeout(function () {
dd
callback.apply(null);
}, 2000);
});
}
//ThirdParty function don't modify anything here
var thirdPartyFunction = function (callback) {
var Domain = require("domain");
var d = require('domain').create();
d.on('error', function (er) {
console.log("hi");
callback.apply(null, er);
});
d.run(function () {
setTimeout(function () {
callback.apply(null, [null, "Hello"]);
}, 1000);
});
};
startFunction(function () {
console.log("Got response")
});
We reported this bug to third party lib and they have modified the source code. Like:
d.on('error', function (er) {
if (isCalled == false) {
isCalled = true;
} else {
return;
}
console.log("hi");
callback.apply(null, er);
});
Now problem of function getting called multiple times is solved. But final callback is never getting called.
How to handle this behavior of node ?
If third party lib modify there code to, It result in application crash. Putting a wrapper domain also not help.
d.on('error', function (er) {
if (isCalled == false) {
isCalled = true;
} else {
throw new Error("Getting called");
return;
}
console.log("hi");
callback.apply(null, er);
});
What is the best method of handing such cases in node ?
You can attach your own domain listener to your callback function like so:
var startFunction = function (callback) {
//Call thirdParty function and wait for response
thirdPartyFunction(function (error, data) {
var d1 = require('domain').create();
d1.on('error', function(er){
console.log("bye");
callback.apply(null, er);
});
d1.run(function () {
console.log("Called with");
console.log(arguments);
//Assume there is an error in my callback function
setTimeout(function () {
dd
callback.apply(null);
}, 2000);
});
})
}
This way if there is an error it will be caught by your handler and the error will be sent back up to the main level and not get caught in the infinite loop.