socket.io, adding message handler dynamically - javascript

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 :)

Related

Callback Mutation Inside Message Event

So I ran into some Javascript weirdness if i write a function like this my passed callback works fine
messageHandler(callback) {
this.ws.onmessage = (data) => {
callback(data.data);
};
}
If you write like this
messageHandler(callback) {
this.ws.on('message', (data) => {
callback(data);
});
}
It mutates the callback into a message event I can't seem to figure out why but I'd like to understand this behavior if anyone can explain. Thanks in Advance.
I think the question omits crucial information but based on the code examples, I think you're using https://github.com/websockets/ws implementation, correct?
If so, then .ws.on('message', ... refers to Event Emitter listener. The params passed to your handler is the message or stream or whatever data object the emitter emits.
.ws.onmessage refers to the browser's Websocket API - so the parameter passed there is a MessageEvent. As you can see from the spec, data is a property on MessageEvent class which explains why you have to refer with 1 level of indirection as data.data.
Perhaps it'd be less confusing if you call your parameter event instead of data in the first example:
this.ws.onmessage = (event) => {
callback(event.data);
};
You can also view this as server side events vs. client side events - your first example is a client side event whereas the second example is a server side event. Client side events conform to Websockets API whereas server side events, in NodeJS world, typically use event emitters.

Nodejs infinitive stream from file read stream

I'm facing troubles trying to accomplish pretty banal task. I need to create nodejs Readable stream from input txt file. I need to perform some transforming on this stream (create JSON object for each line).
Problem is that I want this stream to be infinitive: after last line is read, stream should just start from beginning. My solution works bit I'm getting warning message:
(node) warning: possible EventEmitter memory leak detected. 11 drain listeners added. Use emitter.setMaxListeners() to increase limit.
I hoped to find simple solutions without reading and buffering file directly.
//Transform stream object
var TransformStream = function () {
Transform.call(this, {objectMode: true});
};
util.inherits(TransformStream, Transform);
TransformStream.prototype._transform = onTransform;
TransformStream.prototype._flush = onEnd;
var ts = new TransformStream();
var infinStream = function () {
var r = fs.createReadStream(filePath);
r.pipe(split(), {end: false})
.pipe(ts, {end: false});
r.once('end', function () {
//console.log('\n\n\n\nRead file stream finished. Lines counted:\n\n\n\n' + detectionCounter);
r.removeAllListeners();
r.destroy();
infinStream();
});
return r;
};
infinStream();
return ts;
From the comments:
I need server that will be live for 24h/day, and will simulate device
output all the time.
To do that a recursive function is a good idea. The approach you make is ok. When you don't need different transforming tasks on your data, a stream is not really needed. Simple Events can do exactly what you want and they are easier to understand.
The error in your code is the point, where you put your listener. The listenster r.once is inside your recursive function. You are defining r inside your function, so with every function call a new r is created. Because of that r.once does not work like you are expecting it.
What you can do:
Make a recursive function which emitts an event
use the data from your event outside
This is just a simple concept by using simple events, which are fireing the whole time the data from your file:
// Your recursive function
var simulateDeviceEvents = function(){
fs.readFile('device.txt', function (err, data) {
if (err) throw err;
// Just emit the event here
Emitter.emit('deviceEvent', data);
});
//If this happens to fast you could also call it with
//a timeout.
simulateDeviceEvents();
};
// Start the function
simulateDeviceEvents();
//IMPORTANT: The listener must be defined outside your function!
Emitter.on('deviceEvent', function(data){
// Do something with your data here
});

Return data from jQuery custom trigger/bind event

I am triggering a custom event on an element using jQuery and I want the event handler to be able to pass data (in the form of an object) back to the method that called it. The trigger and the handler are in different scopes and files and cannot be merged or shared in a conventional manner. (The trigger method is part of a utility library I am writing and the event handler is part of my front-end view template).
I know this code doesn't work, but I am writing it to kind of illustrate what I am hoping to try. The pattern is based of of work I have done in .NET.
var data = { foo: 'bar' }
element.trigger('some-event', data)
if(data.foo != 'bar')
alert('someone changed foo!')
And the handler...
element.bind('some-event', function(event, data)
{
if(some_condition)
data.foo = 'biz'
});
The specific implementation is not terrible important to me as long as I don't have rearrange my code to stick both the trigger and the bind in the same scope.
How do I get return value back from my event handler?
EDIT
To provide a little more context, the triggering method is responsible for obtaining and processing data, and finally rendering it out as markup to the page. Before it does that, it raises the custom event with the same processed data object so that other components of the app can have the chance to do something with the processed data.
In certain cases, it would be beneficial for the event handlers to modify that set of data, or event signal to the triggering method that the data has already been handled and does not need additional processing or rendering.
Something like below. In this example, the handler might change the way the data is displayed based upon some property value. It may also choose to render the data out a different way (e.g. input) rather than the default rendering in the triggering function (e.g. label).
This implementation is an example, but the end goals of returning an object from the handler, or modifying the data in the handler such that the triggering method can access it are common to my actual project.
var data = load_data();
element.trigger('loading_data', data);
if(data.abort_render!==true)
{
element.append('<label>Foo</label>')
element.append('<span>' + data.foo + '</span>')
}
And the handler...
element.bind('loading-data', function(event, data)
{
if(data.is_password == true)
{
data.foo = '*******' //changed property persisted to triggering method and processed normally
}
if(data.type == 'abc')
{
element.append('<input value="' + data.foo + '"/>');
data.abort_render = true; //signals to the triggering method that it should not render the data to the page
}
}
There is a way. Below is the jQuery method (modified from Example 2 in this answer: https://stackoverflow.com/a/20707308/870729 )
// Refactor below for efficiency - replace 'body' with closest known container element
jQuery('body').on(element, function(event, data) {
if (data.foo != 'bar') {
alert('someone moved my foo!');
}
});
For a more robust solution, try this:
// Bind the event to the element
jQuery('body').on(element, 'some-event', function(event, data) {
// Call the callback function, pass in the data
eventCallback(data);
});
// Call this function to set up the data and trigger the event
function triggerMyEvent() {
var data = { foo: 'bar' };
element.trigger('some-event', data);
}
// The callback function set in the event binding above
function eventCallback(data) {
if(data.foo != 'bar')
alert('someone changed foo!');
}
The challenge may be in the flow of the logic / code. Below is the code (from your question) - I've added some comments to try and help explain the flow:
// Command 1: This will run first
var data = load_data();
// Command 2: This will run second
element.trigger('loading_data', data);
// Command 3: This will run independently of the trigger callback
// and the data will not be modified based on the trigger.
// You will want to restructure your code to run this within the
// trigger callback
if(data.abort_render!==true)
{
element.append('<label>Foo</label>')
element.append('<span>' + data.foo + '</span>')
}
// Event handler (will run independently of Command 3)
jQuery('body').on(element, 'loading_data', function(event, data) {
// THIS will run independently of the "Command 3" code above
// and may or may not be done before Command 3 is run.
// That's why this needs to trigger the code that handles
// the data checks and various outputs based on the data
handleLoadData(data);
}
// A specific function designed to be run AFTER event handler,
// specifically to handle the various data settings that may be set.
function handleLoadData(data) {
if(data.is_password == true) {
data.foo = '*******' //changed property persisted to triggering method and processed normally
}
if(data.type == 'abc') {
element.append('<input value="' + data.foo + '"/>');
data.abort_render = true; //signals to the triggering method that it should not render the data to the page
}
// This code will need to be moved here, because data may
// not be updated when Command 3 is run above. Alternatively,
// You could put it in another function, and call that function
// from here.
if(data.abort_render!==true) {
element.append('<label>Foo</label>')
element.append('<span>' + data.foo + '</span>')
}
}
Will NOT run in the order you expect. See the comments above to help

Do events fired using event emitter persist order

I have little nooby question about event emitter but it is really important for may program logic.
I am using some external library that fires events I am listening. Lets say it fires 2 events : 'data' and 'error'. Lets say that lib will always call data before error, something like this:
emit('data', 'some data');
emit('error', 'some error');
Question is: can I be 100% sure that data event will always come before error event in my listen methods?:
lib.on('data', function(data) {
// is this always first
});
lib.on('error', function(error) {
// or maybe this
});
Thanks,
Ivan
The EventEmitter emit function is a synchronous blocking function. And hence, like any other blocking function, it is guaranteed to execute in the exact order it is called in.

add to WebSocket.onmessage() like how jQuery adds to events?

I'm writing a single page ws++ site, and I'd like to keep my code grouped first by "page" (I think I need a new word since it never posts back) then by section then by concept etc.
I'd like to split up WebSocket.onmessage across my code much in the same way that $('#someElement') can constantly have an event like click(function(){}) added to it.
Can this be done with WebSocket.onmessage(function(){})? If so, how?
As some jQuery programmers happily know, an event can be initially set then added to in multiple places across the js. That's my favorite thing about js, the "put it anywhere as long as it's in order" ability. This makes code organization so much easier for me at least.
With WebSockets, really, the action client side for me so far is with the WebSocket.onmessage() handler since WebSocket.send() can be used anywhere and really just ports js data to the server.
onmessage() now owns my page, as whatever's in it initiates most major actions such as fading out the login screen to the first content screen upon a "login successful" type message.
According to my limited understanding of js, the onmessage() handler must be set all in one place. It's a pain to keep scrolling back/tabbing to another file to make a change to it after I've changed the js around it, far, far, away.
How can I add to the WebSocket.onmessage() handler in multiple places across the js?
To answer your last question;
how can I add to onmessage handler in multiple places across the js?
You can define your own personal (global) event handler in which you accept arbitrary number of handler functions. Here's an example:
window.bind: function(name, func, context) {
if (typeof this.eventHandlers[name] == "undefined") {
this.eventHandlers[name] = [func];
this.eventContexts[name] = [context];
}
else {
var found = false;
for (var index in this.eventHandlers[name]) {
if (this.eventHandlers[name][index] == func && this.eventContexts[name][index] == context) {
found = true;
break;
}
}
if (!found) {
this.eventHandlers[name].push(func);
this.eventContexts[name].push(context);
}
}
}
window.trigger: function(name, args) {
if (typeof this.eventHandlers[name] != "undefined") {
for (var index in this.eventHandlers[name]) {
var obj = this.eventContexts[name][index];
this.eventHandlers[name][index].apply(obj, [args]);
}
}
}
// === Usage ===
//First you will bind an event handler some where in your code (it could be anywhere since `.bind` method is global).
window.bind("on new email", function(params) { ... });
//Then you need to trigger "on new email" in `onmessage` once appropriate events happen.
WebSocket.onmessage(function(data) {
//Work on data and trigger based on that
window.trigger("on new email", { subject: data.subject, email: data.email });
})
This code is a part of an open source project I worked on before. It gives events names and let you set context for your handler (for methods instead of functions). Then you can call trigger in your onmessage handler of your socket. I hope this is what you are looking for.
You can create a wrapper which will handle WS events on itself. See this example CoffeeScript:
class WebSocketConnection
constructor: (#url) ->
#ws = new WebSocket(#url)
#ws.onmessage = #onMessage
#callbacks = []
addCallback: (callback) ->
#callbacks.push callback
onMessage: (event) =>
for callback in #callbacks
callback.call #, event
# and now use it
conn = new WebSocketConnection(url)
conn.addCallback (event) =>
console.log event
You can do it with addEventListener :
socket.addEventListener('message', function (event) {
console.log('Message from server ', event.data);
});
I've constructed a CoffeeScript class to solve this problem. It's similar to #Valent's but a bit more full-featured, so I figured I'd share it. It provides "on", "off", and "clear" methods for web socket events and also provides forwarding functions for "send" and "close" so that you pretty much don't have to touch the socket directly. If you do need access to the actual WebSocket object, you can get there by superWebSocket.ws.
edit: I added a getConnection static method to produce url-dependent singletons. This way there's only one connection per url and if you attempt to create a 2nd, it just gives you the existing one. It also protects against anyone calling the constructor directly.
edit: I communicate across the socket in JSON. I added some code that will run JSON.stringify on any non-string passed into send and also will attempt to run JSON.parse on any message received via a handler.
superSockets = {}
class SuperWebSocket
#getConnection: (url)->
superSockets[url] ?= new SuperWebSocket url
superSockets[url]
constructor: (url)->
if arguments.callee.caller != SuperWebSocket.getConnection
throw new Error "Calling the SuperWebSocket constructor directly is not allowed. Use SuperWebSocket.getConnection(url)"
#ws = new WebSocket url
events = ['open', 'close', 'message', 'error']
#handlers = {}
events.forEach (event)=>
#handlers[event] = []
#ws["on#{event}"] = (message)=>
if message?
try
message = JSON.parse message.data
catch error
for handler in #handlers[event]
handler message
null
on: (event, handler)=>
#handlers[event] ?= []
#handlers[event].push handler
this
off: (event, handler)=>
handlerIndex = #handlers[event].indexOf handler
if handlerIndex != -1
#handlers[event].splice handlerIndex, 1
this
clear: (event)=>
#handlers[event] = []
this
send: (message)=>
if typeof(message) != 'string'
message = JSON.stringify message
#ws.send message
close: => #ws.close()

Categories

Resources