Stop method/interval if user disconnects or condition is met - javascript

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.

Related

readFile not running inside while loop

I am trying to send the contents of a text file through a socket connection every time the text file updates using Express:
console.log('Server running!');
var express = require('express');
var app = express();
var server = app.listen(3000);
var fs = require("fs");
var x = 0;
app.use(express.static('public'));
var socket = require('socket.io');
var io = socket(server);
io.sockets.on('connection', newConnection);
function newConnection(socket) {
console.log("New connection: " + socket.id);
while (true) {
fs.readFile('song.txt', function(err, data) {
if (err) throw err;
console.log(data);
if (data != x) {
var songdata = data;
console.log(songdata);
io.sockets.emit('update', songdata);
x = data;
} else {
console.log("Song is not different:)");
}
})
}
}
Without the while loop, everything works just fine and I recieve the contents in the seperate client. However, now nothing is happening, no console log of data. This indicates the readFile is suddenly no longer running, why?
Thanks:)
First off, some basics. node.js runs your Javascript as single threaded and thus this is a single threaded server. It can only do one thing with your Javascript at a time. But, if you program it carefully, it can scale really well and do lots of things.
Second off, you pretty much never want to do while (true) in server-side Javascript. That's just going to run forever and never let anything else run on your server. Nothing else.
Third, you are attempting to create a new version of that infinite loop every time a new client connects. That's not a correct design (even if there wasn't an infinite loop). You only need one instance of code checking the file, not N.
Now, if you what you're really trying to do is to "poll" for changes in song.txt and notify the client whenever it changes, you need to pick a reasonable time delta between checks on the file and use a timer. This will check that file every so often and let your server run normally all the rest of the time.
Here's a simple version that polls with setInterval():
console.log('Server code started!');
const express = require('express');
const app = express();
const server = app.listen(3000);
const fs = require("fs");
let lastSongData = 0;
app.use(express.static('public'));
const io = require('socket.io')(server);
// get initial songData for future use
// there will not be any connected clients yet so we don't need to broadcast it
try {
lastSongData = fs.readFileSync('song.txt');
} catch(e) {
console.log(e, "\nDidn't find song.txt on server initialization");
}
// here, we create a polling loop that notifies all connected clients
// any time the song has changed
const pollInterval = 60*1000; // 60 seconds, ideally it should be longer than this
const pollTimer = setInterval(() => {
fs.readFile('song.txt', (err, songData) => {
if (!err && songData !== lastSongData) {
// notify all connect clients
console.log("found changed songData");
io.emit('update', songData);
lastSongData = songData;
}
});
}, pollInterval);
io.sockets.on('connection', socket => {
console.log("New connection: " + socket.id);
});
If your songData is binary, then you will have to change how you send the data to the client and how you compare the data to the previous data so you are sending and receiving binary data, not string data and so you are comparing buffers, not strings.
Here's are some references on sending binary data with socket.io:
How to send binary data with socket.io?
How to send binary data from a Node.js socket.io server to a browser client?
A little more efficient way to detect changes to the file is to use fs.watch() which should notify you of changes to the file though you will have to thoroughly test it on whatever platform you are running to make sure it works the way you want. The feature has a number of platform caveats (it does not work identically on all platforms), so you have to test it thoroughly on your platform to see if you can use it for what you want.
console.log('Server code started!');
const express = require('express');
const app = express();
const server = app.listen(3000);
const fs = require("fs");
let lastSongData = 0;
app.use(express.static('public'));
const io = require('socket.io')(server);
// get initial songData for future use
// there will not be any connected clients yet so we don't need to broadcast it
try {
lastSongData = fs.readFileSync('song.txt');
} catch(e) {
console.log(e, "\nDidn't find song.txt on server initialization");
}
// ask node.js to tell us when song.txt is modified
fs.watch('song.txt', (eventType, filename) => {
// check the file for all eventTypes
fs.readFile('song.txt', (err, songData) => {
if (!err && songData !== lastSongData) {
// notify all connect clients
console.log("found changed songData");
lastSongData = songData;
io.emit('update', songData);
}
});
});
io.sockets.on('connection', socket => {
console.log("New connection: " + socket.id);
});
It is unclear from your original code if you need to send the songData to each new connection (whether it has recently changed or not).
If so, you can just change your connection event handler to this:
io.sockets.on('connection', socket => {
console.log("New connection: " + socket.id);
// send most recent songData to each newly connected client
if (lastSongData) {
socket.emit('update', lastSongData);
}
});
Continuously reading the file to detect changes is not a good idea. Instead you should use fs.watch(filename[, options][, listener]) to notify you when the file has changed. When a new socket connects only that socket should have the content broadcast to them, sending it to every client is redundant.
io.sockets.on('connection', newConnection);
var filename = 'song.txt';
function update(socket) {
fs.readFile(filename, function (err, data) {
if (err) throw err;
socket.emit('update', data);
});
}
function newConnection(socket) {
console.log("New connection: " + socket.id);
update(socket); // Read and send to just this socket
}
fs.watch(filename, function () {
console.log("File changed");
update(io.sockets); // Read and send to all sockets.
});

run user defined function, if number of user connected through sockets is atleast one - Nodejs

I am trying to find a way to stop setInterval(test,5000) function from running if no user is connected to the socket stop setInterval function as it causes lot of waste of resources.
I found the method but I dont know how to put it
io.engine.clientsCount //this will tell number of users connected but only inside socket.on function.
below is my code:
var connectCounter = 0;
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
function test()
{
httpk.get("api-url", function(res) {
var body = '';
res.on('data', function(data){
body += data;
});
res.on('end', function() {
var parsed = JSON.parse(body);
console.log(parsed.johndoe.example1);
nsp.emit('live-quote', parseFloat(parsed.johndoe.example1);
});
});
}
setInterval(test,5000);
nsp.on('connection', function(socket){
//Make a http call
connectCounter++;
nsp.emit('live-users',connectCounter);
console.log('1 user connected, Total Joined: '+connectCounter);
socket.on('disconnect', function(){
connectCounter--;
nsp.emit('live-users',connectCounter);
console.log('1 user disconnected, Total Left: '+connectCounter);
});
console.log("total clients: "+io.engine.clientsCount);
if(io.engine.clientsCount >= 1)
{
//do something
//if I put setInterval here it will cause problems, that is for each connection it will run setInterval causing lot of http get request
// meaning, if 100 users then 100 get request in 5 seconds (depending on setInterval time).
}
});
How do I best stop execution of SetInterval(test,5000) if no users connected?
To stop setInterval, use clearInterval
Refer to
Stop setInterval call in JavaScript
Do you also need help to stop/resume setInterval if you have no/1+ connected user?
If so, you can try managing a reference to your setInterval at the same time as your counter:
var connectCounter = 0;
var interval = undefined;
then:
connectCounter++;
if (interval === undefined) interval = setInterval(test,5000);
then:
connectCounter--;
if (connectCounter <= 0 && interval !== undefined) interval = clearInterval(interval);

Allow a function to be called only after a set amount of time has passed

To provide context, here's the problem I'm attempting to solve:
I've made a giphy bot for a casual groupchat with friends of mine. By typing /giphy [terms] in a message, it will automatically post the top result for [terms]. My friends, being the rambunctious assholes that they are, quickly started abusing it to spam the groupchat. What I would like to do to prevent this is only allow my postMessage function to be called once per minute.
What I've tried:
Using setTimeout(), which doesn't do exactly what I'd like, since it will only call the function after the amount of time specified in the argument has passed. As far as I can tell, this will cause a delay in messages from the time the bot is called, but it won't actually prevent the bot from accepting new postMessage() calls in that time.
Using setInterval(), which just causes the function to be called forever at a certain interval.
What I think might work:
Right now, I'm working with two .js files.
Index.js
var http, director, cool, bot, router, server, port;
http = require('http');
director = require('director');
bot = require('./bot.js');
router = new director.http.Router({
'/' : {
post: bot.respond,
get: ping
}
});
server = http.createServer(function (req, res) {
req.chunks = [];
req.on('data', function (chunk) {
req.chunks.push(chunk.toString());
});
router.dispatch(req, res, function(err) {
res.writeHead(err.status, {"Content-Type": "text/plain"});
res.end(err.message);
});
});
port = Number(process.env.PORT || 5000);
server.listen(port);
function ping() {
this.res.writeHead(200);
this.res.end("This is my giphy side project!");
}
Bot.js
var HTTPS = require('https');
var botID = process.env.BOT_ID;
var giphy = require('giphy-api')();
function respond() {
var request = JSON.parse(this.req.chunks[0]);
var giphyRegex = /^\/giphy (.*)$/;
var botMessage = giphyRegex.exec(request.text);
var offset = Math.floor(Math.random() * 10);
if(request.text && giphyRegex.test(request.text) && botMessage != null) {
this.res.writeHead(200);
giphy.search({
q: botMessage[1],
rating: 'pg-13'
}, function (err, res) {
try {
postMessage(res.data[offset].images.downsized.url);
} catch (err) {
postMessage("There is no gif of that.");
}
});
this.res.end();
} else {
this.res.writeHead(200);
this.res.end();
}
function postMessage(phrase) {
var botResponse, options, body, botReq;
botResponse = phrase;
options = {
hostname: 'api.groupme.com',
path: '/v3/bots/post',
method: 'POST'
};
body = {
"bot_id" : botID,
"text" : botResponse
};
botReq = HTTPS.request(options, function(res) {
if(res.statusCode == 202) {
} else {
console.log('Rejecting bad status code: ' + res.statusCode);
}
});
botReq.on('error', function(err) {
console.log('Error posting message: ' + JSON.stringify(err));
});
botReq.on('timeout', function(err) {
console.log('Timeout posting message: ' + JSON.stringify(err));
});
botReq.end(JSON.stringify(body));
}
exports.respond = respond;
Basically, I'm wondering where would be the ideal place to implement the timer that I'm envisioning. It seems like I would want to have it only listen for /giphy [terms] after one minute, rather than waiting one minute to post.
My Question(s):
Would the best way to go about this be to set a timer on the response() function, since then it will only actually parse the incoming information once per minute? Is there a more elegant place to put this?
How should the timer work on that function? I don't think I can just run response() once every minute, since that seems to mean it'll only parse incoming json from the GroupMe API once per minute, so it could potentially miss incoming messages that I would want it to capture.
Store the time when a request is made and then use that to see if subsequent requests should be ignored if these are executed to fast.
var waitTime = 10*1000; // 10 s in millis
var lastRequestTime = null;
function respond() {
if(lastRequestTime){
var now = new Date();
if(now.getTime() - lastRequestTime.getTime() <= waitTime){
this.res.writeHead(200);
this.res.end("You have to wait "+waitTime/1000+" seconds.");
return;
}
}
lastRequestTime = new Date();
postMessage();
}

Find current client (websocket)

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);
}

Storing the return value of node.js setTimeout in redis

I'm using setTimeout in Node.js and it seems to behave differently from client-side setTimeout in that it returns an object instead of a number. I want to store this in redis, but since redis only stores strings, I need to convert the object to a string. However, using JSON.stringify throws a circular reference error. How can I store this object in redis if I want to be able to fetch it from redis and call clearTimeout on it?
You cannot store the object in Redis. The setTimeout method returns a Handler (object reference).
One idea would be to create your own associative array in memory, and store the index in Redis. For example:
var nextTimerIndex = 0;
var timerMap = {};
var timer = setTimeout(function(timerIndex) {
console.log('Ding!');
// Free timer reference!
delete timerMap[timerIndex];
}, 5 * 1000, nextTimerIndex);
// Store index in Redis...
// Then, store the timer object for later reference
timerMap[nextTimerIndex++] = timer;
// ...
// To clear the timeout
clearTimeout(timerMap[myTimerIndex]);
I was attempting to do the same thing as the OP. My solution was to set the timeout with a conditional check on a new key inside the timeout in my disconnect handler:
redis.hset("userDisconnecting:" + userId, "disconnect", 1);
setTimeout(function() {
redis.hget("userDisconnecting:" + userId, "disconnect",
function(err, result) {
if (result.toString() === "1") {
//do stuff, like notify other clients of the disconnect.
}
});
}, 10000);
Then, when the client connects again, I set that key to 0, so the stuff that needs to fire on true disconnect doesn't happen:
redis.hset("userDisconnecting:" + userId, "disconnect", 0);
The timeouts themselves aren't persistent across server restarts, but you could solve that by kicking off a sweeper method on startup. Connected clients would come back "online" pretty quickly.
In the newer versions of node, you can use the Id of the Timeout object instead of the object itself to end the loop.
redisClient.set('time', JSON.stringify(10))
let timeoutObject = setInterval(async function(){
let time = await JSON.parse(redisClient.get('time'))
if(time === 0){
let intervalId = await JSON.parse(redisClient.get('intervalId'))
clearInterval(intervalId)
}
time -= 1
redisClient.set('time', JSON.stringify(time))
}, 1000)
let intervalId = timeoutObject[Symbol.toPrimitive]()
redisClient.set('intervalId', JSON.stringify(intervalId))
This is just an example of a timer built with setInterval and redis combined. As you can see, you can grab the Id of the Timeout Object and store that to end setInterval's execution instead of trying to store the whole object.
Here is the link to the node docs: https://nodejs.org/api/timers.html#timers_timeout_symbol_toprimitive
This code is used when the timeouts need not be persistent across server restarts
var timeouts = {};
app.get('/', function (req, res) {
var index = timeouts.length;
timeouts[index] = setTimeout(console.log, 1000000, req.user.name);
redis.set('timeout:' + req.user.name, index, function (err, reply) {
res.end();
});
});
app.get('/clear', function (req, res) {
redis.get('timeout:' + req.user.name, function (err, index) {
clearTimeout(timeouts[index]);
delete timeouts[index];
redis.delete('timeout:' + req.user.name);
res.end();
});
});
If you need timeouts to be persistent across server restarts, then you might need to store _idleStart and _idleTimeout values for every timer in the redis, and load them up everytime you server restarts
app.get('/', function (req, res) {
var timeout = setTimeout(console.log, 1000000, req.user.name);
var time = timeout._idleStart.getTime() + timeout._idleTimeout;
redis.set('timeout:' + req.user.name, time, function (err, reply) {
res.end();
});
});
app.get('/clear', function (req, res) {
redis.delete('timeout:' + req.user.name);
res.end();
});
// Load timeouts on server start
// *I know this is not the correct redis command*
// *It's not accurate, only approx*
redis.get('timeout:*', function (err, vals) {
vals.forEach(function (val) {
var time = val - new Date().getTime();
setTimeout(console.log, time, username)
});
});

Categories

Resources