I have a JavaScript Websockets implementation where I would like to use a singleton model that uses one Websocket connection for multiple calls to the server but with different callback event handlers. I have the implementation working just fine but have noticed some strange behaviors with messages being directed to the wrong callback handler. Here is some code:
Connection.js file
var connection = function(){
var _socket = null;
return {
socket : function(){
if (_socket == null){
_socket = new WebSocket("ws://localhost:8081/index.ashx");
_socket.onclose = function(evt){alert('Closed');}
_socket.extraParameter = null;
}
return _socket;
},
send : function(data, callback){
var localSocket = connection.socket();
localSocket.extraParameter = new Date().toString();
localSocket.onmessage = callback;
localSocket.originalDataSent = data;
localSocket.send(data);
}
}
}();
App.js file
var App = function(){
return {
cpuUtilization : function(evt){
var localSocket = this;
var dateTimeOfRequest = localSocket.extraParameter;
var originalDataSent = localSocket.originalDataSent
var jsonData = $.parseJSON(evt.data);
if ($.parseJSON(originalDataSent).type == "cpu"){
$("#dateTimeContainer").html();
$("#cpuContainer").html(jsonData.value);
}
}
}
}();
Third Party Signal.js file
var Signal = function(){
return {
handlerProcess : function(evt){
// Does some third party stuff...
}
}
}();
usage
connection.send("{type:'process'}", Signal.handlerProcess);
connection.send("{type:'cpu'}", App.cpuUtilization);
connection.send("{type:'memory'}", Signal.handlerMemory);
connection.send("{type:'harddrive'}", Signal.handlerHardDrive);
Now where I think I am see the problem is when multiple request are made through the same websocket and the message returns. Since this is asynchronous, I have no way of tieing the request to the event callback. My solution uses the options in the handler for reference, but depending on the time it takes for the websocket request to run, the wrong callback handler is being called and process fails. I think it is failing because I am accessing properties from the websocket instance that may be changing between calls.
Is there a way to pass a reference or additional parameters along with the evt parameter? Maybe wrapping this somehow?
I think it is failing because I am accessing properties from the websocket instance that may be changing between calls.
Yes.
Since this is asynchronous, I have no way of tieing the request to the event callback.
No. You can create a closure for the callback function instead of calling using callback directly:
... send: function(data, callback){
var localSocket = connection.socket();
var extraParameter = new Date().toString();
localSocket.onmessage = function(evt) {
callback(evt.data, /* original- */ data, extraParameter);
};
localSocket.send(data);
}
But still, you have a changing onmessage callback handler. That means, an event may be sent to a handler that does not deserve it. Having an asynchronous system, you will need to add a piece of information to the server resonse that indicates which process the data belongs to. The one universal message handler then could resolve that and call the right callback.
Related
I need to attach an event to every hub.client method.
For example:
Hub.client.doSomething = function (e) {
aFunction(e);
};
Hub.client.doSomethingElse = function (e) {
aFunction(e);
};
Is there a way to attach aFunction() to all client methods on the client level, without placing the function in each client method?
I don't know about such callback available directly on hub proxy, but you could use received callback on connection object. (see list of connection lifetime events and received definition)
Be aware that received callback is called every time data is received by connection, this means, that if you have multiple hubs, it will be invoked when any of hub send data to client. This means, that you will have to inspect data received in callback and decide, if you should process this data (if it belongs to given hub, if it is real message, or just signalr internal message).
With some JavaScript code you can achieve what you need, regardless of what SignalR provides out of the box:
var extend = function (proxy, ex) {
var keys = Object.keys(proxy.client),
aop = function (p, k, f) {
var prev = p[k];
return function () {
f();
prev.apply(p, arguments);
};
};
keys.forEach(function (k) {
proxy.client[k] = aop(proxy.client, k, ex);
});
};
extend(Hub, aFunction);
It is enough to call the extend function on all your hub proxies after having defined your real handlers.
With some more effort this code can be made more solid and generic, but I think it should already put you in the right direction.
I'm trying to accommodate myself to SignalR.
But I need a gold point to completely understand it. I feel one step away from snatch...
My codes:
Javascript:
$(function() {
var connection = $.connection.signalR;
connection.client.broadcastTotalOnline = function (totalOnlineUser) {
$('#TotalOnline').html(totalOnlineUser);
};
$.connection.hub.start().done(function() {
console.log("Connection Started!");
});
});
C#:
public class SignalR : Hub
{
public void SendTotalOnline()
{
var context = GlobalHost.ConnectionManager.GetHubContext<SignalR>();
using (var dbContextx = new db_Oyun())
{
while (true)
{
int totalOnlineUser = dbContextx.tbl_User.Count(x => x.Online);
context.Clients.All.broadcastTotalOnline(totalOnlineUser);
Thread.Sleep(1000);
}
}
}
}
Above codes are work perfect but when I call new page to inner content with AJAX.
And then I would like to use same hub, same object and same hub, different object. It not working. Not calling alert; but when I removed above signalR codes, under codes working.
$(function () {
var connection = $.connection.signalR;
connection.client.broadcastTotalOnline = function (totalOnlineUser) {
alert(totalOnlineUser);
};
});
When you start your SignalR connection, it examines the $.connection.signalR.client object to see if you added any callbacks. If you add a callback using normal assignment after the connection has started, SignalR will not pick up that callback.
To add a callback after the connection has started, you can use $.connection.signalR.client.on:
$.connection.signalR.client.on("broadcastTotalOnline", function (totalOnlineUser) {
alert(totalOnlineUser);
});
If you were trying to add a callback to a Hub you didn't already have a callback for before starting your connection, then even using .on would not work.
You must have at least one callback hooked up to every hub you plan to use before you start up your connection. The callback that you setup can be a dummy callback that is never called. Adding the callback is just you way of indicating to SignalR that your interested in messages originating from that hub.
You don't need to add any dummy callbacks in your case since you already attach to broadcastTotalOnline before starting the connection.
If you want to remove a callback, you can use $.connection.signalR.client.off with a reference to the function you previously added:
var callback = function (totalOnlineUser) {
alert(totalOnlineUser);
};
$.connection.signalR.client.on("broadcastTotalOnline", callback);
$.connection.signalR.client.off("broadcastTotalOnline", callback);
I have a web-app that polls for data periodically to 3rd party services (say Facebook, Twitter, and so on).
This poll/request is made via JSONP (to avoid cross-domain issue).
For example, a simple request would be something like this:
function jsonp_callback() {
// Do something
}
var url = 'http://some.service.com/getresult?callback=jsonp_callback';
$http.jsonp(url);
However since there can be another type of request that can be made at any given time (for example: to send or post an update), I created a wrapper to handle the callbacks.
The implementation is something like this:
// Callback handler
var myCb = (function() {
var F = function() {};
F.prototype.fn = {};
F.prototype.create = function(fn, scope) {
var self = this;
var id = new Date().getTime();
self.fn[id] = function() {
if (typeof fn === 'function') {
fn.call(scope);
}
}
return 'myCb.fn.' + id;
}
return new F();
})();
// JSONP request
var cb = myCb.create(function() {
// Do something
});
var url = 'http://some.service.com/getresult?callback=' + cb;
$http.jsonp(url);
If you notice, after some time, the myCb.fn will be bloated will callbacks that were old or have already executed.
Mu question is, do I need to create a mechanism to garbage-collect those who have been executed and no longer needed?
You don't necessarily need to remove old callbacks, if you will only make a few calls per page, but if your page is a long running one and makes calls repeatedly it could be a good idea to delete them.
The "mechanism" could be as simple as
delete self.fn[id];
after calling the function.
function Observer() {
this.fns = [];
}
Observer.prototype = {
subscribe : function(fn) {
this.fns.push(fn);
},
unsubscribe : function(fn) {
this.fns = this.fns.filter(
function(el) {
if ( el !== fn ) {
return el;
}
}
);
},
fire : function(o, thisObj) {
var scope = thisObj || window;
this.fns.forEach(
function(el) {
el.call(scope, o);
}
);
}
};
var fn = function() {};
var o = new Observer;
o.subscribe(fn);
o.fire('here is my data');
o.unsubscribe(fn);
I am not able to understand the whole concept behind this. I want to implement this pattern in my project. I have a view where the form gets submitted and it calls an WebService and returns me response.
If i have to implement this in my project where this a simple request and response... how would i go about with it? i understand you notify your observer when there is a change... let's take i make a request to my API and i get the response back... now i want it to get notified to my view back through observable pattern
Observer appears to be a constructor that you call with var o = new Observer(); then o will be an object with a reference to a bunch of functions. you add functions to the list via subscribe. and remove them from the list via unsubscribe
then the whole point of it all is the "fire" method which will loop through the function list then call each of the functions one by one . "observer pattern" appears to be a lot like the singleton pattern
Are you familiar with the "watch" method in JavaScript? its a method supported via Firefox that you can use on any object.
document.myform.myfield.watch('value', function (v) {
alert(v);
return v;
})
then whenever the value of the object changes, the watch function is called. so basically the concept behind the observer pattern is that you want to basically simulate Firefox's watch method in a cross-browser fashion
you toss a reference to a bunch of functions or objects into subscribed list.then have Observer.fire call a callback method on each of the watched objects or functions. that way if the user preforms some sort of action such as clicking, then the whole list of functions would be updated via a callback function
I hope this helps.
If you only want to do a simple request then in jQuery (such as with $.ajax(...) or $.get(...)) that would look like this:
var requestUrl = "text.html";
// Callback is defined here
var viewCallback = function(data) {
// this will be called when the request is done
console.log('view is notified');
console.log('data looks like this:');
console.log(data);
// you could chain method calls to other callbacks here if you'd like
};
// Request is done here
$.ajax({
url: requestUrl,
}).done(viewCallback);
Most of the time you only want to do one thing when doing a request for which the above is enough code. Using javascript libraries such as jQuery or mootools will abstract away the oddities with the XMLHttpRequest object.
However if you want to do something much more advanced I'd recommend you look at libraries that do this sort of thing such as Radio.js.
I'm developing a javascript code to run on an embedded device using the ANT Galio browser.
Ideally, I'd like the code to make a get request to another server. After that get request is made, the page would not allow the user to submit another get request, until a response had been received from the previous get request.
For some reason sometimes I am receiving a readyState of 4 almost instantly. It is as though it is evaluating the previous XmlHttpRequest object and not the new one. What am I doing wrong?
<script type="text/javascript">
var fail= function (env, resp, stat) {
alert(resp);
};
var succ= function (env, resp) {
};
var canScan = true;
/* start scan */
function scan (name) {
if (canScan) {
//deactivate button
deactivateScanButtons();
//let server know
ajax = new XMLHttpRequest();
var scanUrl = 'http://19X.1XX.X.XX:8080/scan/' + name
ajax.open('GET', scanUrl, true);
ajax.onreadystatechange = function() {
if (ajax.readyState==4) {
//allow button to work again
activateScanButtons();
alert("ready state 4");
};
};
ajax.send();
//initiate scan
xrxScanInitiateScan(
'http://127.0.0.1',
"ftp.xst",
false,
succ,
fail);
}
}
function deactivateScanButtons () {
// canScan = false;
var indicator = document.getElementById('buttons');
indicator.style.visibility = "hidden";
}
function activateScanButtons () {
// canScan = true;
var indicator = document.getElementById('buttons');
indicator.style.visibility = "visible";
}
</script>
3 suggestions:
To avoid any caching on the client side, add a randomly generated number, or the current timestamp to the request querystring.
As Yoni said, initiate your XMLHttpRequest object with var keyword.
For each request, save the current timestamp within a global variable. In onreadystatechange, call activateScanButtons only if the global timestamp matches the corresponding timestamp of that given request. This way, only the latest request will be able to call activateScanButtons.
You define the ajax object in scan function without the var keyword before it. This means that it is a global object, not local. Afterwards, you have a closure - you refer to that variable in the onreadystate callback function.
I find it hard to track exactly what's going on there, but I agree with you, as you say in your question, that the callback is not using the ajax object the way you expect. You say it happens sometimes - does it happen when you make two requests almost simultaneously (double-click a button or otherwise trigger the two get requests very fast)?
I suggest that you use the var keyword before defining the ajax object. Even better, try using this in the callback function instead of referring to the ajax object by name. If it works, you have spared yourself of one closure.