Node: Storing function names in a variable and then calling them - node.js

I am writing a node application that uses node-cron to schedule certain function to run at specific times. Currently the code for this is embedded in the main application. I want to move the configuration of scheduled tasks out to a config file, so want need to store the function names in a variable to achieve this, as the schedule tasks call out a number of functions in different modules etc.
For information the syntax to schedule a cron task is this:
cron.schedule('* * * * *', () => {functiontocall()});
The code block below shows how I am currently storing the tasks in an object and trying to schedule them with node-cron.
mycronobj = [
{scheduletime : "* * * * *", schedulefunction : 'testfunction1'},
{scheduletime : "*/5 * * * *", schedulefunction : 'testfunction2'},
{scheduletime : "*/10 * * * *", schedulefunction : 'testfunction3'},
]
for (item in mycronobj) {
cron.schedule(mycronobj[item].scheduletime, () => {mycronobj[item].schedulefunction()});
}
However this doesn't work, the scheduled functions don't run. I have tried storing the functions names as a string (as shown) or direct as
{scheduletime : "* * * * *", schedulefunction : testfunction1()}
When trying to add the scheduled function I have tried this with the following syntaxes:
mycronobj[item].schedulefunction()
mycronobj[item]schedulefunction
mycronobj[item].schedulefunction
None of which have worked for me. I have tried looking for an answer to this and I tried using eval(), but this also didn't work correctly, the outcome was that a task with first schedule ("* * * * *") was scheduled with the last function 'testfunction3' was applied, also I dont really want to use eval as I have read its not great practice and can be avoided if you know what your doing (clearly I don't).
Other options I have come across is to use Window which doesn't exist in Node or This, which I cannot get to work either.
Thanks in advance.

The reason your code isn't working is that mycronobj[item]schedulefunction is a string so is not invokable. You need to turn this string into a reference to the function with the same name.
The simplest solution to this is using eval, e.g:
for (item of mycronobj) {
cron.schedule(item.scheduletime, () =>
eval(`${item.schedulefunction}()`);
}
However this is a really bad idea, and allows for arbitrary code execution which is generally considered a very bad thing (tm).
Node gives you a slightly safer alternative to this using the vm module, which can create a limited sandbox in which your code can execute:
const vm = require("vm");
let context = vm.createContext({ foo });
for (item of mycronobj) {
cron.schedule(item.scheduletime, () =>
vm.runInContext(`${item.schedulefunction}()`, context);
}
This is only marginally better though since it can still allow arbitrary code execution, so by far the safest option is just to explicitly list exactly which functions are allowed to be called:
const commands = {
testfunction1: () => console.log("I am 1"),
testfunction2: () => console.log("I am 2"),
testfunction3 // Reference existing function
}
function testfunction3() { console.log("I am 3"); }
for (item of mycronobj) {
let func = commands[item.schedulefunction];
if (!func) { throw new Error(`Unknown command: "${item.schedulefunction}"`); }
cron.schedule(item.scheduletime, func);
}
This also has the benefit of determining during setup whether functions are valid rather than only when the cron job runs.

Related

Avoiding more than one cronjob in parallel in node.js

I would like to do some cronjob using the Node.js package "cron" every 10 minutes.
This cronjob takes between 5 to 15 minutes, and I don't want that in a case that one instance is still running - another will be joining it in parallel. Instead, it will skip the additional running and wait until the next period.
Is it possible to implement it using this package?
Here is the code of the implementation using the cron package :
const CronJob = require("cron").CronJob;
const job = new CronJob(
'0 */10 * * * *',
()=>SomeCronJob(),
null,
true,
'America/Los_Angeles',
);
I thought of implementing it using a combination of simple setInterval() and clearInterval() instead of the package, not sure how though.
I'd appreciate any help!
I would use a flag to check if the job is running. Example:
let isJobRunning = false;
function SomeCronJob() {
if (isJobRunning) {
// Skip
return;
}
isJobRunning = true;
// run stuff
// Once is finished
isJobRunning = false;
}

Stopping and starting execution of cron-job-manager (node-cron) upon condition

So, I found an old question about this (here: How to stop a node cron job) but it doesn't work for me (and I can't realize the reason why).
I'm using cron-job-manager (since I plan to use more than one scheduled job at the same time) but as far as I know it should be built on node-cron (but I'm a newbie, so...)
So, I'm asking again: how do I deal with starting and stopping a cron job under certain conditions?
I'm actually doing this for a discord bot: basically, the user input a command for starting and one for stopping.
First try was something like:
job = cron('keyTask','* * * * * *', ()=>{
//Do the actual job
},
{
start: false,
timeZone:"Europe/London",
});
switch args[1]
case go:
job.start();
break;
case stop:
job.stop();
break;
This start the job successfully when the user gives the 'go' command, but when it gives the 'stop' command, the scheduled job just won't stop.
Second try is:
var x = args [1];
new cron('keyTask' , '* * * * * *', job(doTheThing(x)) ,
{
start: false,
timeZone:"Europe/London",
});
Where job() is a function defined beforehand that contains the actual job and DoTheThing() is something that gives true or false depending on what the user is saying in input.
This executes the scheduled job once and then stops.
Somehow I suspect that the issue here is related to the fact that I'm defining function externally, while in most examples I saw the function is always written within the cron().
So, I'm out of ideas: thanks in advance for any help!

node-cron looping endlessly on vm

Im having a cron job set up on a gcloud compute instance.
It runs as expected on my local, but on the gcloud instance it waits until the first run and then starts to execute in an endless loop without a time gap.
async function runScript() {
...
}
cron.schedule("* */30 * * * *", () => {
runScript();
});
So I still dont know what the issue was, but I figured out a solution.
The cron-job needs to be setup in the following way:
cron.schedule("0 */30 * * * *", () => {
Now it also runs fine on the vm

How to use Generic cron jobs in node application?

I have node application in which I want to run tasks on daily basis. So I want to use node-cron to schedule tasks but not like this:
var cron = require('node-cron');
cron.schedule('* * * * *', function(){
console.log('running a task every minute');
});
I need some generic solution so that at one place I have just empty body for cron job that takes different functions from a different location. That means in total we have two files, one which has just cron job function handler and other have a list of cron jobs. From where we want to run these jobs we just need require and some basic calling.
Can anyone suggest me this solution?
I got my solution. Using node-cron-job module I have achieved what I wanted. Let me explain in detail:-
First i created new file which contains jobs, lets name it jobs.js :-
// jobs.js
exports.first_job = {
on:"* * * * * " //runs every minute
},
job: function () {
console.log("first_job");
},
spawn: true
}
exports.second_job = {
on: "*/2 * * * * *", //runs every 2 second
job: function () {
console.log("second_job");
},
spawn: false // If false, the job will not run in a separate process.
}
Here, we define two jobs name first_job and second_job. We can define as many required.
Finally, we need to call these jobs from any location by these simple steps:-
// main.js
var cronjob = require('node-cron-job');
cronjob.setJobsPath(__dirname + '/jobs.js'); // Absolute path to the jobs module.
cronjob.startJob('first_job');
cronjob.startJob('second_job');
We can call all jobs in a single call like this:-
cronjob.startAllJobs();

How to retreieve jobs with specific status in kue?

I am using kue for my job queue, and I'd like to know without using the GUI how many jobs are still left, how many have failed, etc. How can I retrieve this kind of information?
For example, after a few minutes of starting the processing of the job queue, I'd like to o update the status of all jobs that failed so far to 'inactive', in order to restart them.
The only related question I could find on stackoverflow was this, however, it deals with one job at a time, after it fires a certain event as it is being processed. My concern is different, as I am interested in retrieving all jobs in the database with a certain status.
The answer to this question mentions the function .complete of the kue library, which retrieves all the completed jobs in the database. Are there similar functions for other possible job statuses?
I found a solution by browsing the kue source code. The following code achieves what I need:
var redis = require ('redis'),
kue = require ('kue'),
redisClient = redis.createClient(6379, "127.0.0.1");
kue.redis.createClient = function () {
return redisClient;
};
kue.app.listen(3000);
kue.Job.rangeByType ('job', 'failed', 0, 10, 'asc', function (err, selectedJobs) {
selectedJobs.forEach(function (job) {
job.state('inactive').save();
});
});
For reference, here is the relevant kue source code:
/queue/job.js:123:
/**
* Get jobs of `type` and `state`, with the range `from`..`to`
* and invoke callback `fn(err, ids)`.
*
* #param {String} type
* #param {String} state
* #param {Number} from
* #param {Number} to
* #param {String} order
* #param {Function} fn
* #api public
*/
exports.rangeByType = function(type, state, from, to, order, fn){
redis.client().zrange('q:jobs:' + type + ':' + state, from, to, get(fn, order));
};
Kue source code indicating that:
type is the job type
from, to is the job ranges by index (for example, you can specify load jobs from index 0 to 10, 11 jobs in total.)
order specifies the order of fetched jobs. Default is asc. You can also sort it by desc
The following works, uses the pre-existing queue object and hence, no double Redis connection issue as mentioned by Japrescott in the comments of the accepted answer.
queue.cardByType("notifications", "complete", function( err, count ) {
console.log(count);
});
Feel free to replace with a valid state, the following is a list of valid states.
inactive
complete
active
failed
delayed

Resources