Get Timer ID in Node.js - javascript

I have a simple server-side timer in NodeJS application to send some data with socket to client. How do I manage to send Timer ID to client so it can be stopped from a client side?
I tried to pass returned class in socket payload, but got an error "Max call stack size exceeded.
socket.on('start', () => {
const timerID = setInterval(()=>{
socket.emit('data',someData)
},3000)
socket.emit('ID',timerId)
}
Also tried to map over class symbols, but nothing works.
const array = Object.getOwnPropertySymbols(timerID);
array.map((symbol) => {
clearInterval(timerID[symbol]);
});
Found in NodeJS Documentation this:
timeout[Symbol.toPrimitive]"()
Returns: number that can be used to reference this timeout
But it also didn't work.

The client cannot stop your timerID and, in nodejs a timerID is not a simple integer either or something that can be stringified so it can't effectively be sent to the client.
You could create your own integer identifier that is mapped to the actual timerID, send that to the client and then the client could send the server back a message that it wishes to cancel timer X. You would look up timerX in your Map, find the real timerID and stop the timer.
const timerMap = new Map();
const timerCntr = 0;
socket.on('start', () => {
const timerID = setInterval(()=>{
socket.emit('data', someData)
}, 3000);
// create an integer reference for the timer
// save a mapping between the integer reference and the actual timerID
// in a server-side Map object
const externalTimerID = timerCntr++;
timerMap.set(externalTimerID, timerID);
socket.emit('ID', externalTimerID);
}
// when client wants to stop the timer
socket.on('stop', externalTimerID => {
let timerID = timerMap.get(externalTimerID);
clearInterval(timerID);
// remove externalTimerID from the Map so we don't leak memory
timerMap.delete(externalTimerID);
});
Starting with node v14.9.0, you can get an integer version of the timerID that will work as a substitute for the actual timerID object.
socket.on('start', () => {
const timerID = setInterval(()=>{
socket.emit('data', someData)
}, 3000);
socket.emit('ID', timerID[Symbol.toPrimitive]());
}
// when client wants to stop the timer
socket.on('stop', externalTimerID => {
clearInterval(externalTimerID );
});
Here's a sample nodejs app where I verified that timerID[Symbol.toPrimitive]() is a number and will work as a substitute for the timerID object.
const timerID = setInterval(() => {
console.log(`hi`);
}, 500);
const integerID = timerID[Symbol.toPrimitive]();
console.log(typeof integerID, integerID); // shows it is an integer
setTimeout(() => {
console.log("clearing Interval with integerID");
clearInterval(integerID);
}, 2000);

Related

How can I stop the interval?

I'm making a setInterval method in my discord bot, but I have an issue in stopping the Interval.
Look at my code:
const Discord = require('discord.js');
const client = new Discord.Client();
client.once('ready', () => {
console.log('Im online');
});
client.on('message', async msg => {
var interval;
if(msg.content == '!spam')
{
interval = setInterval(() => {
msg.channel.send('test');
}, 2000);
}
if(msg.content.startsWith('!stop'))
{
clearInterval(interval);
}
});
That should definitely work, right?
If you know the answer please help me!!
The problem is:
client.on('message', async msg => {
var interval;
This creates a new variable binding interval every single time the message handler runs. Inside any single message handler, either
interval = setInterval(() => {
msg.channel.send('test');
}, 2000);
will run, or
if (msg.content.startsWith('!stop')) {
clearInterval(interval);
}
will run (or neither will run). But the interval is not persistent outside of the function, so further messages will be attempting to clear a different interval binding.
Make interval persistent instead, and also check that you don't start an interval while one is already going on:
let interval;
client.on('message', (msg) => {
if (msg.content == '!spam' && !interval) {
interval = setInterval(() => {
msg.channel.send('test');
}, 2000);
}
if (msg.content.startsWith('!stop')) {
clearInterval(interval);
interval = null;
}
});

clearInterval in web worker not stopping timer

my question has been asked once here:
clearInterval in webworker is not working
The solution seems clear but for some reason it is not working for me yet. I have a web worker that is sending an interval back to the main thread. I want to be able to stop the interval with clearInterval, but it is not working.
I have it set up exactly the same as it suggests in the previous question, but still no luck. I have added some console.logs to verify I'm in the correct block. "Stop" logs to the console when it supposed to, but the timer doesn't stop posting to the main thread.
Can anyone spot what's going on here?
Thanks
worker.js
let mytimer;
self.onmessage = function(evt) {
if (evt.data == "start") {
console.log("start")
var i = 0;
mytimer = setInterval(function() {
i++;
postMessage(i);
}, 1000);
} else if (evt.data == "stop") {
console.log("stop")
clearInterval(mytimer);
}
};
Then I'm calling this from my React hook when timer.time is above or below a certain value (2000 in this case)
main.js
const worker = new myWorker()
useEffect(() => {
worker.addEventListener('message', function(e) {
//from interval in the worker
console.log('Message from Worker: ' + e.data);
})
if(timer.time > 2000){
worker.postMessage("start")
}else{
worker.postMessage("stop")
}
},[timer.time])
You should also clear the interval when you start a new interval. If you don't do it, your previous interval would keep running, and you'll lose the ability to clear it:
let mytimer;
self.onmessage = function(evt) {
console.log(evt.data)
if(evt.data === 'start' || evt.data === 'stop') {
clearInterval(mytimer);
}
if (evt.data == "start") {
var i = 0;
mytimer = setInterval(function() {
i++;
postMessage(i);
}, 1000);
}
};
You should create a single instance of the worker, and store it as ref:
const worker = useRef()
useEffect(() => {
worker.current = new myWorker()
return () => {
worker.current.terminate();
}
}, [])
Not related, but in addition, the useEffect adds a new event listener whenever timer.time changes, without clearing the previous one. I would split this into 2 useEffect blocks, one for sending (which can be combind with the creation of the worker), and the other for receiving.
useEffect(() => {
const eventHander = e => {
//from interval in the worker
console.log('Message from Worker: ' + e.data);
}
worker.current.addEventListener('message', eventHander)
return () => {
worker.current.removeEventListener('message', eventHander)
}
}, [])
useEffect(() => {
worker.current.postMessage(timer.time > 2000 ? 'start' : 'stop')
}, [timer.time])
I'm not sure about web workers but I am very familiar with using intervals in useEffect. Since useEffect is called everytime your dependencies change (timer.time), you need to store the interval in a useRef unless you are going to be clearing it before your dependency next changes

How can I unsubscribe or cancel the filtering of a large array that is an RxJS observable?

My understanding is that an entire array is pushed to a subscriber, unlike say an interval observer that can be unsubscribed/cancelled.
For example the following cancellation works...
// emit a value every second for approx 10 seconds
let obs = Rx.Observable.interval(1000)
.take(10)
let sub = obs.subscribe(console.log);
// but cancel after approx 4 seconds
setTimeout(() => {
console.log('cancelling');
sub.unsubscribe()
}, 4000);
<script src="https://unpkg.com/rxjs#5.5.10/bundles/Rx.min.js"></script>
However, replacing the interval with an array doesn't.
// emit a range
let largeArray = [...Array(9999).keys()];
let obs = Rx.Observable.from(largeArray)
let sub = obs.subscribe(console.log);
// but cancel after approx 1ms
setTimeout(() => {
console.log('cancelling');
sub.unsubscribe()
}, 1);
// ... doesn't cancel
<script src="https://unpkg.com/rxjs#5.5.10/bundles/Rx.min.js"></script>
Does each element need to be made asynchronous somehow, for example by wrapping it in setTimeout(..., 0)? Perhaps I've been staring at this problem too long and I'm totally off course in thinking that the processing of an array can be cancelled?
When using from(...) on an array all of the values will be emitted synchronously which doesn't allow any execution time to be granted to the setTimeout that you are using to unsubscribe. Infact, it finishes emitting before the line for the setTimeout is even reached. To allow the emits to not hog the thread you could use the async scheduler (from(..., Rx.Scheduler.async)) which will schedule work using setInterval.
Here are the docs: https://github.com/ReactiveX/rxjs/blob/master/doc/scheduler.md#scheduler-types
Here is a running example. I had to up the timeout to 100 to allow more room to breath. This will slow down your execution of-course. I don't know the reason that you are attempting this. We could probably provide some better advice if you could share the exact use-case.
// emit a range
let largeArray = [...Array(9999).keys()];
let obs = Rx.Observable.from(largeArray, Rx.Scheduler.async);
let sub = obs.subscribe(console.log);
// but cancel after approx 1ms
setTimeout(() => {
console.log('cancelling');
sub.unsubscribe()
}, 100);
// ... doesn't cancel
<script src="https://unpkg.com/rxjs#5.5.10/bundles/Rx.min.js"></script>
I've marked #bygrace's answer correct. Much appreciated! As mentioned in the comment to his answer, I'm posting a custom implementation of an observable that does support such cancellation for interest ...
const observable = stream => {
let timerID;
return {
subscribe: observer => {
timerID = setInterval(() => {
if (stream.length === 0) {
observer.complete();
clearInterval(timerID);
timerID = undefined;
}
else {
observer.next(stream.shift());
}
}, 0);
return {
unsubscribe: () => {
if (timerID) {
clearInterval(timerID);
timerID = undefined;
observer.cancelled();
}
}
}
}
}
}
// will count to 9999 in the console ...
let largeArray = [...Array(9999).keys()];
let obs = observable(largeArray);
let sub = obs.subscribe({
next: a => console.log(a),
cancelled: () => console.log('cancelled')
});
// except I cancel it here
setTimeout(sub.unsubscribe, 200);

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

Using setInterval() to do simplistic continuous polling

For a simple web app that needs to refresh parts of data presented to the user in set intervals, are there any downsides to just using setInterval() to get a JSON from an endpoint instead of using a proper polling framework?
For the sake of an example, let's say I'm refreshing the status of a processing job every 5 seconds.
From my comment:
I would use setTimeout [docs] and always call it when the previous response was received. This way you avoid possible congestion or function stacking or whatever you want to call it, in case a request/response takes longer than your interval.
So something like this:
function refresh() {
// make Ajax call here, inside the callback call:
setTimeout(refresh, 5000);
// ...
}
// initial call, or just call refresh directly
setTimeout(refresh, 5000);
A simple non-blocking poll function can be implemented in recent browsers using Promises:
var sleep = duration => new Promise(resolve => setTimeout(resolve, duration))
var poll = (promiseFn, duration) => promiseFn().then(
sleep(duration).then(() => poll(promiseFn, duration)))
// Greet the World every second
poll(() => new Promise(() => console.log('Hello World!')), 1000)
You can do just like this:
var i = 0, loop_length = 50, loop_speed = 100;
function loop(){
i+= 1;
/* Here is your code. Balabala...*/
if (i===loop_length) clearInterval(handler);
}
var handler = setInterval(loop, loop_speed);
Just modify #bschlueter's answer, and yes, you can cancel this poll function by calling cancelCallback()
let cancelCallback = () => {};
var sleep = (period) => {
return new Promise((resolve) => {
cancelCallback = () => {
console.log("Canceling...");
// send cancel message...
return resolve('Canceled');
}
setTimeout(() => {
resolve("tick");
}, period)
})
}
var poll = (promiseFn, period, timeout) => promiseFn().then(() => {
let asleep = async(period) => {
let respond = await sleep(period);
// if you need to do something as soon as sleep finished
console.log("sleep just finished, do something...");
return respond;
}
// just check if cancelCallback is empty function,
// if yes, set a time out to run cancelCallback()
if (cancelCallback.toString() === "() => {}") {
console.log("set timout to run cancelCallback()")
setTimeout(() => {
cancelCallback()
}, timeout);
}
asleep(period).then((respond) => {
// check if sleep canceled, if not, continue to poll
if (respond !== 'Canceled') {
poll(promiseFn, period);
} else {
console.log(respond);
}
})
// do something1...
console.log("do something1...");
})
poll(() => new Promise((resolve) => {
console.log('Hello World!');
resolve(); //you need resolve to jump into .then()
}), 3000, 10000);
// do something2...
console.log("do something2....")
I know this is an old question but I stumbled over it, and in the StackOverflow way of doing things I thought I might improve it. You might want to consider a solution similar to what's described here which is known as long polling. OR another solution is WebSockets (one of the better implementations of websockets with the primary objective of working on all browsers) socket.io.
The first solution is basically summarized as you send a single AJAX request and wait for a response before sending an additional one, then once the response has been delivered, queue up the next query.
Meanwhile, on the backend you don't return a response until the status changes. So, in your scenario, you would utilize a while loop that would continue until the status changed, then return the changed status to the page. I really like this solution. As the answer linked above indicates, this is what facebook does (or at least has done in the past).
socket.io is basically the jQuery of Websockets, so that whichever browser your users are in you can establish a socket connection that can push data to the page (without polling at all). This is closer to a Blackberry's instant notifications, which - if you're going for instant, it's the best solution.

Categories

Resources