I'm trying to use WebSockets for the first time, and I'm making a todo list app. I'm using Express and Socket.io and Redis.
In the following code, socket.emit('items', {items: redisItems}) is failing, saying that emit can't be done on undefined. I know that client.hgetall is an asynchronous call and that I have to somehow get it to execute the after. How do I fix it? Do I use something like Futures or can this be done with a small fix on what I have right now?
io.sockets.on('connection', function (socket) {
socket.on('getItems', function (socket){
var redisItems = new Array;
callback = function(err,obj){
for ( att in obj) {
item = new Object();
item.key = att;
item.done = obj[att];
redisItems.push(item);
console.log(redisItems.length);
console.log(item.key);
}
socket.emit('items', {items: redisItems})
};
client.hgetall("items", callback);
});
});
Sidenote: What I currently have goes like this: 1) browser requests page 2) Once the browser has the page, it connects through WebSocket and asks for the todo items (so 2 requests).
In the future, I would prefer something like 1) browser makes request for page 2) server gives page and sends items once it's done.
You're overwriting your socket object in the getItems callback. While the initial io.sockets connection event returns the socket, subsequent socket.on events don't return another socket object, they return data for the event. Try something like:
io.sockets.on('connection', function (socket) {
socket.on('getItems', function (data){
var redisItems = new Array;
callback = function(err,obj){
for ( att in obj) {
item = new Object();
item.key = att;
item.done = obj[att];
redisItems.push(item);
console.log(redisItems.length);
console.log(item.key);
}
socket.emit('items', {items: redisItems})
};
client.hgetall("items", callback);
});
});
Related
I want to create a live order page where clients can see the status of their order.
For that reason I want to run a function every 10 seconds that checks the SQL database if the order is ready.
function checkOrder(socket, userid, checkinterval) {
pool.getConnection(function(err, connection) {
// Use the connection
connection.query('SELECT * FROM orders WHERE user = ' + userid + ' ORDER BY timestamp DESC', function(err, rows) {
var alldone = false;
for (var i = 0; i < rows.length; i++) {
if (rows[i]['status'] == 'completed') {
alldone = true;
} else {
alldone = false;
break;
}
}
socket.emit('order-update', rows);
connection.release();
if (alldone) {
console.log('all done');
socket.emit('execute', '$("#orderstatus").html(\'Done\');');
clearInterval(checkinterval);
}
});
});
}
var express = require('express');
var app = express();
var app = express();
var options = {
key: fs.readFileSync('privkey.pem'),
cert: fs.readFileSync('cert.pem'),
ca: fs.readFileSync("chain.pem")
};
var server = require('https').createServer(options, app);
var io = require('socket.io')(server);
var port = 443;
server.listen(port, function() {
console.log('Server listening at port %d', port);
});
io.on('connection', function(socket) {
socket.on('trackorder', function(userid) {
var checkinterval = setInterval(function() {
checkOrder(socket, userid, checkinterval);
}, 10000);
});
socket.on('disconnect', function() {
clearInterval(checkinterval);
});
});
Now I'm having issues on stopping the function if either the job is completed or the client disconnects.
How could I achieve that? I suppose the clearInterval() would work inside the function since it is passed but there is an issue with the on disconnect event handler. Either checkinterval is undefined or if I define it globally it stops the wrong function.
How can this be done properly?
Your checkInterval variable is out of scope when the disconnect event comes. You need to move its definition up a level.
io.on('connection', function(socket) {
// checkInterval variable is declared at this scope so all event handlers can access it
var checkInterval;
socket.on('trackorder', function(userid) {
// make sure we never overwrite a checkInterval that is running
clearInterval(checkInterval);
checkInterval = setInterval(function() {
checkOrder(socket, userid, checkInterval);
}, 10000);
});
socket.on('disconnect', function() {
clearInterval(checkinterval);
});
});
In addition:
I added a guard against overwriting the checkInterval variable if you ever get the trackorder event more than once for the same client.
You mispelled checkinterval in one place.
As others have said, polling your database on behalf of every single client is a BAD design and will not scale. You need to either use database triggers (so it will tell you when something interesting changed) or have your own code that makes relevant changes to the database trigger a change. Do not poll on behalf of every single client.
You have no error handling in either pool.getConnection() or connection.query().
Instead of that complicated setInterval stuff, just add a small IIFE that calls itself if the result isnt there yet. Some pseudocode:
function checkOrder(socket, userid){
//a variable pointing to the running timer
var timer;
//on error clear
socket.on("disconnect", ()=>clearTimout(timer));
//a small IIFE
(function retry(){
pool.getConnection(function(err, connection) {
//parse & notice socket
if (!alldone) //retry
timer = setTimeout(retry, 1000);
});
})();
}
I would say you're using a bad approach. You should go for push rather than pull.
What I mean is, emit the event when status of order changes. Don't put the burden on your database to hit it frequently for no reason.
On successful change of status, emit the event order_status_update with order id and what is the new status
socket.emit('order_status_update', {order_id: 57, status: 'In Process'});
This way you don't need any kind of loop or setinterval etc. No worries even if client is connected or not, its sockat.io business to take care of it. You will just raise the event.
I try to assign a value to a variable in onmessage function here:
socket = new WebSocket(ws_scheme +"127.0.0.1:8000/chat/");
var incomeData;
socket.onmessage = function(e) {
incomeData='hello'+ e.data;
console.log('inside function:'+incomeData)
}
socket.onopen = function() {
socket.send(" from websocket");
}
console.log(incomeData)
the console will show the first log as undefined and the second "inside function: hello from websocket". How can I get the variable assigned and keep the value outside the function too? Why second console.log appears first ?
incomeData is defined at global scope and is not assigned anything when first is printed with console.log(incomeData) thus you get the undefined error message. Then the socket onmessage is called and you got is assigned the e.data value that console.log then prints out.
You can handle the socket.onmessage and pass the e.data to a callback function like this:
socket = new WebSocket(ws_scheme +"127.0.0.1:8000/chat/");
socket.onmessage = function(e) {
socket_onmessage_callback(e.data);
}
socket.onopen = function() {
socket.send(" from websocket");
}
function socket_onmessage_callback(data) {
console.log('inside function socket_onmessage_callback:', data)
}
You can even use incomeData global variable if you want, but I think it does not required. Can update my answer if you need to use incomeData.
Once you get the main point of websocket protocol, you can easily update your global variables via a callback function which you pass to websocket.onmessage method of the protocol. The main point is that the backend server sends information to the client NOT in response to client's GETs or POSTs or whatever, but when it wants. There is no way to force a websocket endpoint to return you anything, it does it when it wants, even if you haven't asked for it. Once you have opened the ws:// connection, get ready for receiving data through this connection ANY unexpected time. What you need to do is to make sure that your app won't crash on an initial (or an undefined) value of the variable to be assigned, and just let the server update your variable when the server (not you!) wants. Here is a working code example with a self-invoking ws:// connection initialization (the (function(...) {...})(...); construction) and a callback:
<script>
var yourGlobalField = undefined;
console.log("Value before update: ", yourGlobalField);
(function initWebsocketConnection() {
websocket = new WebSocket("ws://your.websocket.URI");
websocket.onmessage = function (evt) {
theFunctionNeededToMakeSureWeHaveReceivedRealData(evt, yourSuccessCallbackFunction(evt));
};
websocket.onerror = function(err) {
networkError(err);
}
})();
function theFunctionNeededToMakeSureWeHaveReceivedRealData(evt) {
console.log("we have received some data!");
}
function yourSuccessCallbackFunction(data) {
yourGlobalField = JSON.parse(data); //just assuming you receive a JSON object
console.log("Value after update: ", yourGlobalField)
}
function networkError(err) {
console.log('Request Failed', err);
}
</script>
I am using something like the following code to save all clients in an array...
var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({port: 8080}),
CLIENTS=[];
wss.on('connection', function(ws) {
CLIENTS.push(ws);
ws.on('message', function(message) {
console.log('received: %s', message);
findClient(message);
});
ws.send("NEW USER JOINED");
});
function findClient (message) {
for (var i=0; i<CLIENTS.length; i++) {
//this is where I'm stuck
if current client then return i
}
}
I do not know what to put inside the for loop to find the current client. I want to iterate through the array, and if the current client == one of the client in the array, I want to return its index.
I'm sure there is a simple way to do this, but I am stuck.
Do findClient(ws) -- you already have the ws (socket) belonging to that particular client bound to the closure of the message event handler. Then the findClient function becomes:
function getClientIndex(socket) {
return CLIENTS.indexOf(socket);
}
My setup is as follows:
Nodejs Server
server.js requires utils.js
utils.js loads data from mongodb into memory and exports it
server.js uses a variable that utils.js exports
The issue that I am worried about is the fact that the mongodb call is asynchronous. utils.js returns before the mongodb call is finished, meaning that server.js will use an undefined variable when it continues execution after the require.
What is the best to address this issue? The only thing I could think of is wrapping my server.js code in a giant callback and pass that to the function that makes the mongodb call. It seems a bit messy to me, is there a better way to do it?
Code:
server.js
var utils = require("./modules/utils.js");
console.log(utils);
//Do whatever
utils.js
var mods = [];
var db = require("mongojs").connect("localhost", ["modules"]);
db.modules.find({}, function(err, modules){
mods = modules;
});
module.exports = mods;
What you're referring to is called "callback hell". The easiest way to get out of that is to use a Promise library that simplifies it.
I used a node package called bluebird.
var mysql = require("mysql");
var hash = require("password-hash");
var Promise = require("bluebird");
var settings = require("../settings");
Promise.promisifyAll(require("mysql/lib/Connection").prototype);
Promise.promisifyAll(require("mysql/lib/Pool").prototype);
var db_config = {
user:settings.db.user,
password:settings.db.password,
database:settings.db.database
};
var con = mysql.createPool(db_config);
function query(sql) {
return con.getConnectionAsync().then(function(connection) {
return connection.queryAsync(sql)
.spread(function(rows,fields) {
return rows;
}).finally(function() {
connection.release();
});
});
}
This is a very basic database module I wrote that uses bluebird to promisify the database object.
And here's how it's used. It returns a promise! The benefit here is that not only does it return the clutter of callback hell, it makes sure that your code runs asynchronously and the function does not return before things have stopped processing, like in this case, a database query.
function login(user) {
//check for player existance
var query = 'SELECT p.name,p.password,p.id, pd.x, pd.y FROM player p INNER JOIN player_data pd ON p.id = pd.id WHERE p.name='+mysql.escape(user);
return db.select(query).then(function(rows) {
if (!rows.length) return;
return [
rows[0]
];
});
}
Notice how you return a promise, so that you call the then or spread method to get those database values you just queried, not having to worry about if rows will be undefined by the time you want to use it.
As you say, you need to wrap the entire server in a callback. Node.js works this way, it's asynchronous by nature. A server needs to pass through 3 stages: init, serve and deinit. In your case, that database code goes inside the init stage. You could write something like this.
//server.js
var utils = require ("./modules/utils");
var init = function (cb){
//init the utils module, the http server and whatever you need
utils.init (function (error){
if (error) return handleError (error);
cb ();
});
};
var serve = function (){
//Start listening to the http requests
};
var deinit = function (cb){
//This is typically executed when a SIGINT is received, see link1
};
init (serve);
//utils.js
//You could write a wrapper for the db instance, see link2
var mongodb = require ("mongojs");
var db;
module.exports.init = function (cb){
db = mongodb.connect ("localhost", ["modules"]);
db.modules.find ({}, function (err, modules){
if (err) return cb (err);
cb (null, modules);
});
};
I don't recommend using promises, they are slower than raw callbacks. You don't need them at all.
link1 - SIGINT
link2 - db wrapper
I have the following function:
getTasks: function()
{
var taskRequest = Titanium.Network.createHTTPClient();
var api_url = 'http://myawesomeapi.heroku.com/users/' + Ti.App.Properties.getString("userID") + '/tasks';
var tasks = [];
taskRequest.onload = function() {
var response = JSON.parse(this.responseText),
len = response.length,
i = 0,
t;
for(; i < len; i++)
{
task = response[i];
var newTask = {};
newTask.rowID = i;
newTask.title = task.title;
newTask.description = task.description;
newTask.id = task.id;
newTask.hasChild = true;
tasks.push(newTask);
}
alert(tasks);
}
taskRequest.open('GET', api_url, false);
taskRequest.setRequestHeader('Content-Type', 'application/json');
taskRequest.send();
alert(tasks);
// return tasks;
}
This function is in my controller; I call it in my view when I need to load the data in. However, I wish to return this data so I can assign it to a variable in the view.
Now what happens is that it returns emptiness. The last alert (bottom one) seems to be running too fast and it returns an empty array, while the one that only gets alerted after the onload function is done, contains what I need.
Now my obvious question, how can I get my function to return the array with the data, instead of without?
Putting a timer on it seems hardly the right decision.. Thanks!
"However, I wish to return this data so I can assign it to a variable in the view."
Aside from making the AJAX request synchronous (which you probably don't want), there isn't any way to return the data.
Whatever code relies on the response needs to be called from within the response handler.
Since functions can be passed around, you could have your getTasks method receive a callback function that is invoked and will receive the tasks Array.
getTasks: function( callback ) // receive a callback function
{
var taskRequest = Titanium.Network.createHTTPClient();
var api_url = 'http://myawesomeapi.heroku.com/users/' + Ti.App.Properties.getString("userID") + '/tasks';
taskRequest.onload = function() {
var tasks = [];
// code populating the tasks array
alert(tasks);
callback( tasks ); // invoke the callback
}
taskRequest.open('GET', api_url, false);
taskRequest.setRequestHeader('Content-Type', 'application/json');
taskRequest.send();
}
So you'd use it like this...
myObj.getTasks(function(tasks) {
alert('in the callback');
alert(tasks);
// Any and all code that relies on the response must be
// placed (or invoked from) inside here
some_other_function();
});
function some_other_function() {
// Some more logic that can't run until the tasks have been received.
// You could pass the tasks to this function if needed.
}
You are getting empty alert because when the bottom alert is executed the server response is not available and tasks array is empty.
When the server response comes the tasks array is populated by the code which you have in the onload handler so you see the tasks in the second alert.