Call API at scheduled time - javascript

I was trying to run an API call at a scheduled time. I researched through sites and found this package called node-schedule from npmjs. This is working as expected by calling the code at required time. The issue I am having is :
Suppose I have a list of times eg: ["10:00","11:00","13:00"]
Once I start the server, it will get executed at required times. But what if I want to change the time list dynamically?
Exactly what I am trying to do:
Call API and get times from Database
Setup cron-schedule for each of these times.
Dynamically adding new time to database
What I want: Dynamically add this newly added time to the cron-schedule
index.js
const express = require('express');
const schedule = require('node-schedule');
const app = express();
const port = 5000;
var date = new Date(2019, 5, 04, 14, 05, 20);// API call here
var j = schedule.scheduleJob(date, function(){
console.log('The world is going to end today.');
});
app.get('/test', (req, res) => {
var date = new Date(2019, 5, 04, 14, 11, 0); // Will call API here
var q = schedule.scheduleJob(date, function(){
console.log('Hurray!!');
});
res.send('hello there');
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
The code written above is what I have and its pretty messed up. What I am conveying there is that, while running the index.js file API is called and the cron-schedule gets executed. Now if there is some new values added to DB I want to rerun this.
Rerunning index.js is another option I have, but I don't think its the right thing to do. The next option I have in mind is to call another endpoint which is mentioned as /test above, which would eventually run the cron again.
Please let me know some suggestions or some sort of solution to this so I can rectify my mistakes.

With this code I think you could do what you want, although you must adapt it to your needs in terms of defining the functions that will execute the tasks or if you need to specify the times in which they will be executed in another way (setting specific days of the week, for example).
var times = [];
var tasks = [];
function addTask(time, fn) {
var timeArr = time.split(':');
var cronString = timeArr[1] + ' ' + timeArr[0] + ' * * *';
// according to https://github.com/node-schedule/node-schedule#cron-style-scheduling
var newTask = schedule.scheduleJob(cronString, fn);
// check if there was a task defined for this time and overwrite it
// this code would not allow inserting two tasks that are executed at the same time
var idx = times.indexOf(time);
if (idx > -1) tasks[idx] = newTask;
else {
times.push(time);
tasks.push(newTask);
}
}
function cancelTask(time) {
// https://github.com/node-schedule/node-schedule#jobcancelreschedule
var idx = times.indexOf(time);
if (idx > -1) {
tasks[idx].cancel();
tasks.splice(idx, 1);
times.splice(idx, 1);
}
}
function init(tasks) {
for (var i in tasks){
addTask(i, tasks[i]);
}
}
init({
"10:00": function(){ console.log("It's 10:00"); },
"11:00": function(){ console.log("It's 11:00"); },
"13:00": function(){ console.log("It's 13:00"); }
});
app.post('/addTask', (req, res) => {
if (!req.body.time.match(/^(0[0-9]|1[0-9]|2[0-3]|[0-9]):[0-5][0-9]$/)) {
// regex from https://stackoverflow.com/a/7536768/8296184
return res.status(400).json({'success': false, 'code': 'ERR_TIME'});
}
function fn() {
// I suppose you will not use this feature just to do console.logs
// and not sure how you plan to do the logic to create new tasks
console.log("It's " + req.body.time);
}
addTask(req.body.time, fn);
res.status(200).json({'success': true});
});

Related

my strings will not be re-declared in my loop

I have been programming a Battlesnake (https://play.battlesnake.com/) and I need to re-declare my move commands (like move="left" and move="right" for example) command but every time it goes over the code it uses the move command once and won't use it again. It also only uses the first one it can and won't use any other ones above it. Loops won't work because to my understanding the Battlesnake servers call the handleMove function each turn and if the Battlesnake servers don't get a response within 500 milliseconds the game will move for you. The code starts at the top of the handleMove and ends at the response.status thing. I know the code goes through the functions because I tracked it with console.log. The code goes through everything as intended, because I set console.log on all of the functions and inside all the if commands, and it runs through the move strings inside the if commands, but ignores all move commands it reaches after the code gets to response.status. That is where I am confused. A lot of the data is hosted on the Battlesnake servers, so if something is not declared in my code it probably came from the Battlesnake servers.
How can I fix this?
const express = require('express')
const PORT = process.env.PORT || 3000
const app = express()
app.use(bodyParser.json())
app.get('/', handleIndex)
app.post('/start', handleStart)
app.post('/move', handleMove)
app.post('/end', handleEnd)
app.listen(PORT, () => console.log(`Battlesnake Server listening at http://127.0.0.1:${PORT}`))
function handleIndex(request, response) {
var battlesnakeInfo = {
apiversion: '1',
author: '',
color: '#1c607d',
head: 'caffeine',
tail: 'leaf'
}
response.status(200).json(battlesnakeInfo)
}
function handleStart(request, response) {
var gameData = request.body
console.log('START')
response.status(200).send('ok')
}
function handleMove(request, response) {
var gameData = request.body
var head = gameData.you.head
var boardWidth = gameData.board.width
var boardHeight = gameData.board.height
var food = gameData.board.food[0]
// code i wrote
function moveUpB() {
if (head.x < 1) {
move = "up"
//refuses to execute at all??
}
}
function moveRightB() {
if (head.y > (boardHeight - 2)) {
move = "right"
//only used once
}
}
function moveDownB() {
if (head.x > (boardWidth - 2)) {
move = "down"
//only used once
}
}
function moveLeftB() {
if (head.y < 1) {
move = "left"
//only used once
}
}
moveUpB()
moveRightB()
moveDownB()
moveLeftB()
//aafter here it should go back to moveUpB but it doesent?
//rest of the code needed for the snake to move
console.log('MOVE: ' + move)
response.status(200).send({
move: move
})
}
function handleEnd(request, response) {
var gameData = request.body
console.log('END')
response.status(200).send('ok')
}

setInterval Not Repeating Function using Discord.js

I'm trying to make a discord bot that gives you weekly reminders. Im using momentjs to get the time. As well as using discord.js-commando. I found the best way to call a function multiple times is to use setInterval.
const moment = require('moment');
const Commando = require('discord.js-commando');
const bot = new Commando.Client();
bot.on('ready', function () {
var day = moment().format('dddd');
var time = moment().format('h:mm a');
function Tuesday() {
if (day == 'Tuesday' && time == '6:30 pm') {
const channel = bot.channels.get('933047404645724234');
console.log('Sending Message');
channel.send('Reminder: You have a week to complete your To-Do List!');
} else {
console.log('Not Sending Message');
}
}
console.log('Bot is now ready!');
setInterval(Tuesday, 100);
});
I noticed the problem with the setInterval is that it is only called once. I also tried using an async function but that failed as well.
Well executing a function every 100ms is not quite optimal. I don't know what is the reason to run just once (it should run infinitely) but there is a better way to do what you need.
You will need the package called "node-schedule". It's really useful for such things.
Here's an example:
const schedule = require("node-schedule");
schedule.scheduleJob({hour: 18, minute: 30, dayOfWeek: 2}, function(){
// your code here
});
You can read more in the documentation of node-schedule here.

Nodejs function running every 2 minutes lost over time

This is quite hard problem to describe.
I have a koajs app with a function which is created in multiple instances (10-1000 range) every 2 minutes. this scheduled job created on app startup. I use koajs because i need a few simple api endpoints for this app. It is running well for first 3-5 hours and then the count of created instances starts to decrease and some of the log output disappears.
Here is the minimal sample based on actual code:
server.ts
const bootstrap = async () => {
process.setMaxListeners(0); //(node:7310) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 uncaughtException listeners added to [process]. Use emitter.setMaxListeners() to increase limit
//appears on app startup (however seems like this setMaxListeners(0) doesnt affect anything since the warning persist)
const app = new Koa();
app.use(async ctx => {
ctx.body = "Welcome to my Server!";
});
app.listen(port);
new Main().run();
};
bootstrap();
main.ts (tried: cron npm package, node-scheduler, setInterval, recursive setTimeout) to run the scheduledJobWrapper.
isStarting: boolean = false;
async run() {
logger.info(`running the app, every 2 minutes`);
//let that = this;
// new CronJob(`*/2 * * * *`, function () {
// that.scheduledJobWrapper();
// }, null, true, 'America/Los_Angeles');
const interval = 2 * 60 * 1000;
setInterval(() => {
this.scheduledJobWrapper();
}, interval);
}
async scheduledJobWrapper() {
logger.info("here scheduledJobWrapper");
let args = {};
//some irrelevant logic to set the arguments
await this.scheduledJob(args);
}
async scheduledJob(args) {
try {
logger.info("starting");
if (!this.isStarting) {
this.isStarting = true;
const runningCount = Executor.tasks.length; //Executor.tasks is a singleton containing some info about tasks. details are irrelevant. the point is it contains the active tasks.
const tasksLimit = 100;
if (runningCount < tasksLimit) {
for await (const i of Array(tasksLimit - runningCount).keys()) {
if (Executor.tasks.length > 20)
await global.sleep(5 * 1000);
this.startWrapper(args); //calling main task here
}
}
this.isStarting = false;
logger.info(`Started: ${Executor.tasks.length - runningCount}`);
}
} catch (e) {
logger.error("Error running scheduled job: " + e.toString());
}
}
In this example the problem manifests as following:
All work as expected first 3-5 hours, later for each time the scheduled function called:
logger.info("here scheduledJobWrapper"); does now show any output.
logger.info("starting"); not in the output
this.startWrapper does run and the code inside it is being executed.
Despite that the code inside of this.startWrapper is still running, the count of newly created jobs is slowly decreasing.
Hardware (RAM/CPU) is not getting any significant load (CPU under 10%, RAM under 20%)
Any clue on possible reason?
nodejs: 12.6.0
Thanks!
UPDATE
it seems like that with the usage of setInterval the app is running OK for a longer time (6-24 hours), but after that the problem still starts.
The issue is with setInterval function. It gets slow down with the time. It has wierd behavior too. You can create custom setInterval using setTimeout or use third-party module and give try.
Sample setInterval Implementation.
const intervals = new Map();
function setInterval(fn, time, context, ...args) {
const id = new Date().getTime() + "" + Math.floor(Math.random() * 10000);
intervals.set(
id,
setTimeout(function next() {
intervals.set(id, setTimeout(next, time));
fn.apply(context, args);
}, time)
);
return id;
}
function clearInterval(id) {
clearTimeout(intervals.get(id));
}
setInterval(console.log, 100, console, "hi");
You can also enhance, by adding delta time loss in next setTimeout.
Meaning if time loss, run next setTimeout earlier.
First of all, It will be better to move instance of Main() in listen scope:
app.listen(port, () => {
new Main().run();
});
I don't know how good idea is to run setInterval function in the backend side. It's better to extract this logic and move it in cron job.
Are we sure that the machine can run 100 tasks? Please count the tasks by order and see when the problem starts. Probably you can not schedule 100 tasks and exists one limit somewhere

Best way to call periodically (twice a day) an API from Node.js to get data

I've got my Node.js application which use the Fixer.io API to get some data. Currently the call is made when I reach the URL myapp/rate. My goal is to make this call automatically twice a day to store the data in my MongoDB database.
So I would like to know what is the best way to do that ? Maybe the setInterval() is the only way to do it but I don't think so...
My call looks like this :
router.get('/', (req, res, next) => {
fixer.latest({ base: 'EUR', symbols: ['CAD'] })
.then((data) => {
var currentData = data.rates;
if (currentData) {
const currentRate = new Rate({ rate: currentData.CAD });
currentRate.save((err) => {
if (err) {
throw err;
} else {
console.log('Data saved successfully!');
}
});
}
})
.then(() => {
Rate.find({}, { rate: 1, _id: 0 }, (err, rates) => {
if (err) {
throw err;
} else {
res.json(rates);
}
});
})
.catch(() => {
console.log('Error');
});
});
Thank's
You can use the node module node-schedule to run a task within your node app on a cron-like schedule.
var schedule = require('node-schedule');
var j = schedule.scheduleJob('0 0 0,12 * *', function(){
console.log('This will run twice per day, midnight, and midday');
});
The key word in your question is 'automatically'. The easiest (and most reliable) way to run a script at a particular time of day on the server would be to use cronjobs on the server. That way your server will execute the script regardless of the user interaction:
0 0 * * * TheCommand // 12 a.m.
0 12 * * * TheCommand // 12 p.m.
However, it is also possible to run a script at a particular time of day using a JavaScript setTimeout() as you were thinking. You need to calculate the current time, the target time, and the difference between them:
var now = new Date();
var payload = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 12, 0, 0, 0) - now;
if (payload < 0) {
payload += 46400000; // Try again 12 hours later
}
setTimeout(function(){ // YOUR DESIRED FUNCTION }, payload);
Note the 12 above in payload is the hour at which you want the script to run. In this example, I'm running the script at both 12 p.m. and 12 a.m.
Keep in mind that this will only work if the application is permanently running the page where the JavaScript is loaded; there's no way of forcing JavaScript to run 'in the background' as it is a client-side language.
Hope this helps! :)

Set and Clear Timeout on Node Server from Client

I'm attempting to allow a user to set an alarm from the client and pass it to the server. The server then has a setTimeout that counts down and when time runs out, executes the function.
This first part is working fine, however, I need the the ability to clear that same timeout, should the client decide to cancel that particular alarm.
Note: I've been storing various data using Redis, so that is available.
var client = redis.createClient();
io.set("store", new sio.RedisStore);
io.sockets.on('connection', function (socket) {
socket.on('alarm:config', function(list, date, time, bool) {
if (bool) {
var now = new Date().getTime();
var year = date[0],
month = date[1] - 1,
day = date[2];
var hour = time[0],
minutes = time[1];
var alarm = new Date(year, month, day, hour, minutes);
var countdown = alarm - now;
var myAlarm = setTimeout(function() {
// do stuff...
}, ( countdown ) );
} else {
clearTimeout(myAlarm);
}
});
});
The approach I have in mind is that I would use the boolean value to determine if the user is setting or canceling that particular alarm. I realize that setting a local variable "myAlarm" will not work, I just put it there to convey the idea.
I am trying to figure out a way to store a reference to that exact timeout so that the next time the "alarm:config" socket event is triggered with a false boolean value, it can cancel the timeout that was set earlier.
It might be another question all together, but how does an application like Google Calendar store a date and time and then know exactly when to trigger it as well as offer the ability to cancel it? This would essentially be the same idea.
UPDATE: I have it working using the following solution. I am open to a more elegant solution.
socket.on('alarm:config', function(list, date, time, bool) {
var alarmName = "timer:" + list;
if (bool) {
client.hset(alarmName, "status", true);
var now = new Date().getTime();
var year = date[0],
month = date[1] - 1,
day = date[2];
var hour = time[0],
minutes = time[1];
var alarm = new Date(year, month, day, hour, minutes);
var countdown = alarm - now;
setTimeout(function() {
client.hget(alarmName, "status", function(err, bool) {
if(bool == 'true') {
// do stuff...
} else {
console.log("This alarm has been canceled.");
}
});
}, ( countdown ) );
} else {
console.log('canceling alarm');
client.hset(alarmName, "status", false);
}
});
Depending on how large of an application you're building, there are a couple of options.
Processing Queue
You could restructure your application to use a job queue instead of simply setting timers. This has an advantage that you can split it in the future into multiple processes, but it does complicate the handling a bit.
A library like Kue uses just Redis and allows you to do a delayed put to set events in the future.
Going from the Kue readme:
var kue = require('kue')
, jobs = kue.createQueue();
// Create delayed job (need to store email.id in redis as well)
var email = jobs.create('email', {
title: 'Account renewal required',
to: 'tj#learnboost.com',
template: 'renewal-email'
}).delay(minute).save();
// Process job
jobs.process('email', function(job, done){
email(job.data.to, done);
});
// Cancel job
email.remove(function(err){
if (err) throw err;
console.log('removed completed job #%d', job.id);
});
Storing Reference to Timeout
This is much less robust, but could allow you to do it very simply. It leaves global variables floating around, and has no recovery if the process crashes.
Basically, you store the timer in a global variable and cancel it based on the job id.
var myAlarms = {}
socket.on('alarm:config', function(list, date, time, bool) {
var alarmName = "timer:" + list;
if (bool) {
myAlarms[alarmName] = setTimeout(function() {
// do stuff...
}, countdown);
} else {
clearTimeout(myAlarms[alarmName]);
}
}
Note: None of this code has been tested and it likely contains errors.

Categories

Resources