Is there a way to put socket.io events into functions? - javascript

I wanna encapsulate socket.io events into functions to make it more readable for my project, here is an example. I am trying to put this:
io.on('connection', (socket) =>{
//code
});
Into something like this:
function isThereANewConnection() {
io.on('connection', (socket) =>{
//..return true?
});
}
function update() {
if(isThereANewConnection()) {
//do something with the socket data..
}
}
I cannot seem to figure out how i could implement this since i cannot return something from the function. Does anyone know how to do this?

You haven't really explained what you're trying to accomplish by putting "events into functions" so it's hard to know precisely what you want it to look like.
At it's heart, nodejs is an event driven environment. As such, you register event listeners and they call you when there's an event. That's the structure of the socket.io connection event.
If you don't want everything inline such as this:
io.on('connection', (socket) =>{
//code
});
You can put the event listener callback into a function itself such as:
io.on('connection', onConnection);
Then, somewhere else (either in the same module or imported into this module), you would define the onConnection function.
function onConnection(socket) {
// put your code here to handle incoming socket.io connections
socket.on('someMsg', someData => {
// handle incoming message here
});
}
Note: inline callback functions (of the type you seem to be asking to avoid) are very common in nodejs programming. One of the reasons for using them is that when they are inline, they have available to them all the variables from the parent scope (without having to pass them all as arguments) and there are times this is very useful. So, it might be a style that you will find useful rather than avoiding.

I don't think this is going to structure your code in a way that is more readable and useful to you. But if you did want this, you could use async/await.
async function getNewConnection() {
return await new Promise((resolve, reject) => {
io.on('connection', resolve);
});
}
Then in your other code:
const socket = await getNewConnection();
Really though, for this connection handler you should stick with the callback pattern. It keeps things simple for you. Run your updates when the connection comes in, and not the other way around.

Update: I have kinda found the OOP solution for this (it is not the best):
class ConnectionHandler {
init() {
this.listenForConnections();
}
listenForConnections() {
io.on('connection', (socket) =>{
//do stuff
this.newConnection = true;
this.update();
});
}
// the update method contains all the logical decisions of a class
update() {
if(this.isThereANewConnection()) {
//add connection to list.. or handle the rest of the logic
}
//render screen
}
isThereANewConnection() {
if(this.newConnection) {
this.newConnection = false;
return true;
}
return false;
}
}

Related

Nodejs, Sinon: How to test some functions are called upon an event

Say I have an object in my code, let's say a User.
User is an eventEmitter so in many places in the code I do like:
User.on('register', notifyFriends);
User.on('register', sendWelcomeEmail);
This lines are in different files in principal.
I want to make sure that the relevant functions are called in my test so I do something like this:
describe('User Events', () => {
before(() => {
sinon.stub(userListeners, 'notifyFriends').callsFake(async () => {});
});
after(() => {
userListeners.notifyFriends.restore();
});
describe('register', () => {
it('should trigger the "notifyFriends" handler once user is registered', () => {
UserModel.emit('register');
expect(userListeners.notifyFriends).to.be.calledOnce();
});
});
});
Problem is the the function is run after the expect statement so the test fails obviously.
An easy way to solve it is to run the expect inside a Timeout block and hope that the function is called within the given time.
But I want to know if there is a better, more elegant way to do it.
The issue was different.
The code that registers to the handlers was executed before the stub was made. (So the function wasn't a spy yet)
So make sure to run the stub, only after run the rest of the code in the application that registers to the evetns.

Starting Alexa Skill in a specific state

Earlier I ran into the issue of Alexa not changing the state back to the blank state, and found out that there is a bug in doing that. To avoid this issue altogether, I decided that I wanted to force my skill to always begin with START_MODE.
I used this as my reference, where they set the state of the skill by doing alexa.state = constants.states.START before alexa.execute() at Line 55. However, when I do the same in my code, it does not work.
Below is what my skill currently looks like:
exports.newSessionHandler = {
LaunchRequest () {
this.hander.state = states.START;
// Do something
}
};
exports.stateHandler = Alexa.CreateStateHandler(states.START, {
LaunchRequest () {
this.emit("LaunchRequest");
},
IntentA () {
// Do something
},
Unhandled () {
// Do something
}
});
I'm using Bespoken-tools to test this skill with Mocha, and when I directly feed IntentA like so:
alexa.intended("IntentA", {}, function (err, p) { /*...*/ })
The test complains, Error: No 'Unhandled' function defined for event: Unhandled. From what I gather, this can only mean that the skill, at launch, is in the blank state (because I have not defined any Unhandled for that state), which must mean that alexa.state isn't really a thing. But then that makes me wonder how they made it work in the example code above.
I guess a workaround to this would be to create an alias for every intent that I expect to have in the START_MODE, by doing:
IntentA () {
this.handler.state = states.START;
this.emitWithState("IntentA");
}
But I want to know if there is a way to force my skill to start in a specific state because that looks like a much, much better solution in my eyes.
The problem is that when you get a LaunchRequest, there is no state, as you discovered. If you look at the official Alexa examples, you will see that they solve this by doing what you said, making an 'alias' intent for all of their intents and just using them to change the state and then call themselves using 'emitWithState'.
This is likely the best way to handle it, as it gives you the most control over what state and intent is called.
Another option, assuming you want EVERY new session to start with the same state, is to leverage the 'NewSession' event. this event is triggered before a launch request, and all new sessions are funneled through it. your code will look somewhat like this:
NewSession () {
if(this.event.request.type === Events.LAUNCH_REQUEST) {
this.emit('LaunchRequest');
} else if (this.event.request.type === "IntentRequest") {
this.handler.state = states.START;
this.emitWithState(this.event.request.intent.name);
}
};
A full example of this can be seen here (check out the Handlers.js file): https://github.com/alexa/skill-sample-node-device-address-api/tree/master/src
I would also recommend reading through this section on the Alexa GitHub: https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs#making-skill-state-management-simpler
EDIT:
I took a second look at the reference you provided, and it looks like they are setting the state outside of an alexa handler. So, assuming you wanted to mimic what they are doing, you would not set the state in your Intent handler, but rather the Lambda handler itself (where you create the alexa object).
exports.handler = function (event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = appId;
alexa.registerHandlers(
handlers,
stateHandlers,
);
alexa.state = START_MODE;
alexa.execute();
};

socket.io, adding message handler dynamically

I've written happily a node.js server, which uses socket.io to communicate with the client.
this all works well.
the socket.on('connection'...) handler got a bit big, which made me think of an alternative way to organize my code and add the handlers in a generator function like this:
sessionSockets.on('connection', function (err, socket, session) {
control.generator.apply(socket, [session]);
}
the generator takes an object that contains the socket events and their respective handler function:
var config = {
//handler for event 'a'
a: function(data){
console.log('a');
},
//handler for event 'b'
b: function(data){
console.log('b');
}
};
function generator(session){
//set up socket.io handlers as per config
for(var method in config){
console.log('CONTROL: adding handler for '+method);
//'this' is the socket, generator is called in this way
this.on(method, function(data){
console.log('CONTROL: received '+method);
config[method].apply(this, data);
});
}
};
I was hoping that this would add the socket event handlers to the socket, which it kind of does, but when any event comes in, it always calls the latest one added, in this case always the b-function.
Anyone any clues what i am doing wrong here?
The problem appears because by that time this.on callback triggers (let's say in a few seconds after you bind it), the for loop is finished and method variable becomes the last value.
To fix that you may use some JavaScript magic:
//set up socket.io handlers as per config
var socket = this;
for(var method in config){
console.log('CONTROL: adding handler for '+method);
(function(realMethod) {
socket.on(realMethod, function(data){
console.log('CONTROL: received '+realMethod);
config[realMethod].apply(this, data);
});
})(method); //declare function and call it immediately (passing the current method)
}
This "magic" is hard to understand when you first see it, but when you get it, the things become clear :)

File watching in Node.js

I'm writing a Node.js server that watches a directory full of empty files for changes. When a file changes, it notifies a client, then empties the file. The watch code is:
fs.watch("./files/", function(event, targetfile){
console.log(targetfile, 'is', event)
fs.readFile("./files/"+targetfile, 'utf8', function (err,data) {
if (err) {
return console.log(err);
}
if (data=="") return; //This should keep it from happening
//Updates the client here
fs.truncate("./files/"+targetfile, 0);
});
});
The change event happens twice, thus the client gets updated twice. This can't happen. Its like the watch function is being called twice at the same time, and both execute before either can get to the truncate command. How do I keep this from happening? I can't, say, block a thread because I need it to be responsive in real time for the other files.
Thank you for the help. I'm new to Node.js, but so far I'm loving it.
You can use the underscore utility method Once that keeps the function from executing more than once. You'd have to make your code look like this:
var func = _.once(function(targetfile){
fs.readFile("./files/"+targetfile, 'utf8', function (err,data) {
if (err) {
return console.log(err);
}
if (data=="") return; //This should keep it from happening
//Updates the client here
fs.truncate("./files/"+targetfile, 0);
});
});
fs.watch("./files/", function(event, targetfile){
console.log(targetfile, 'is', event);
func(targetfile);
});
If you want it executed more than once, but you want to filter out duplicate events, you can use a function such as throttle or debounce.

How to run mocha tests in a chronological order?

I've got a set of modules that run based on a global event emitter. They run based on a chronological chain of events, like so:
boot.ready
server created (because of boot.ready event)
server configured (because of server.created event)
As such, I need to create a server-test.js that performs tests in a chronological order.
Is this possible with Mocha? Something like the following?
var EventEmitter2 = require('eventemitter2').EventEmitter2,
should = require('should');
describe('server', function() {
var mediator = new EventEmitter2({
wildcard: false
});
require('../../src/routines/server/creator')(mediator);
require('../../src/routines/server/configurer')(mediator);
it('should be created after boot', function(done) {
mediator.once('server.created', function(server) {
server.should.exist;
done();
});
it('should be configured after created', function(done) {
mediator.once('server.configured', function() {
done();
});
});
mediator.emit('boot.ready');
});
});
Because there seemed to be some confusion about the way this global event emitter works, this is the server/creator.js module:
module.exports = function(mediator) {
var express = require('express');
mediator.once('boot.ready', function() {
var server = express.createServer();
//event: server created
mediator.emit('server.created', server);
});
};
As you can see, the server is created after boot.ready. This fires server.created, after which the configurer will run which will then fire server.configured.
This chain of events needs to be tested by mocha.
If I'm testing a chain of events the quick way is to do it is nested eventEmitter.once calls like this:
it('executes in the right sequence', function(done) {
mediator.once('boot.ready', function() {
mediator.once('server.created', function() {
done()
})
})
})
edit: as pointed out server.created will be fired before the test's boot.ready handler is fired. Here's a workaround:
it('executes in the right sequence', function(done) {
var bootReadyFired = false
mediator.once('boot.ready', function() {
bootReadyFired = true
})
mediator.once('server.created', function() {
assert.ok(bootReadyFired)
done()
})
})
Hope this helps.
Actually mocha use function.length to your it callbacks to know if you want them asynchronously, so with function(done) you can't known in which order they're run. function() without done argument will run them synchronously.
EDIT
Your mediator is an EventEmitter2 meaning that when you emit something, the handler will be run async. Like I said they's no way to known the order in which the are executed.
The problem is in you required modules, each event should probably be emitted in the handler of the previous. Code is better than words :
// require('../../src/routines/server/creator')(mediator);
// I guess this module creates the server, then it should emit server.created
// require('../../src/routines/server/configurer')(mediator)
// This one should listen to server.created then configure the server
// and finally emit server.configured
// So in it you should have something like this:
mediator.once('server.created', function(server) {
// Here you should configure you're server
// Once it is configured then you emit server.configured
});
Also you should know that emit is immediate in node so you'd better add your listeners before emitting.
Hope this is clear.

Categories

Resources