Tasks scheduling in Node.js

In this post I will describe several ways of scheduling tasks in Node.js using standard JavaScript methods like setTimeout() and setInterval(), but also some libraries like Bree or cron with a way more sophisticated scheduling configuration mechanisms.

Tasks scheduling is straightforward especially Node.js provides some standard methods simplifying that exercise. However, sometimes you may require to execute tasks at specific times (every Monday at 3AM or every weekday at 11:30AM) and then tasks scheduling becomes a way more challenging. In these cases, 3rd party library support will save us lots of time and effort.

One-off execution of a task

JavaScript has the setTimeout() method, which allows one-off execution of a function. The method accepts parameters as follows:

  • func - a function to be executed
  • delay - a number of milliseconds the execution of the given function should be delayed by. If this parameter is omitted or value 0 is used, the provided function will be executed as soon as possible.

An example of a message printed after 2 seconds:

setTimeout(() => console.log('Task completed'), 2000)

The setTimeout() method returns an identifier, which can be used to cancel a particular execution using the clearTimeout() method.

An example of a task cancelation:

const taksID = setTimeout(() => console.log('Task completed'), 2000)
clearTimeout(taskID)

Repetitive executions of a task

JavaScript has the setInterval() method, which allows continues execution of a function at specified intervals. The method accepts parameters as follows:

  • func - a function to be executed
  • delay - a number of milliseconds between executions of the given function. If the value of this parameter is below 10, a value of 10 is used.

An example of a message printed every 5 seconds:

setInterval(() => console.log('Task executed'), 5000)

The setInterval() method returns an identifier, which can be used to cancel a particular repetitive execution using the clearInterval() method.

An example of a repetitive task cancelation:

const intervalID = setInterval(() => console.log('Task executed'), 5000)
clearInterval(intervalID)

Tasks execution at specific times

The setTimeout() and setIntervals() are straightforward to use. But what if I need to execute a task every Monday at 3AM or at midnight on the 3rd day of a month? In these cases, the default JavaScript methods are not so handy anymore. Of course, you can still use them, but you would need to calculate delay on your own. Say, you need to execute a task at midnight on the 3rd day of a month. You would need to calculate the number of milliseconds since now till next midnights on the 3rd and provide that value to setTimeout(). After a task execution, you would need to perform another calculation of a delay till midnight on the 3rd following month, and so on and on. That’s feasible but very time consuming and not a maintainable solution (every, even the smallest change of times, would require you to update and test your delay calculation code).

So what options do we have? Use of 3rd party Node.js module seems to be a very good idea to resolve that problem.

There are several libraries that solves the problem os scheduling tasks using cron syntax. I use a lot node-cron . However, recently I came across another job scheduler for Node.js - Bree , which supports a way more task scheduling features. I could recommend both libraries to everyone looking for a robust and configurable tasks scheduler.

Basic example of node-cron usage

const cron = require('cron')
const job = cron.job('* * * * *', () => console.log('Message every minute'))
job.start()

Basic example of Bree usage

const Bree = require('bree');

const bree = new Bree({
  jobs: [
    // Run starup job on Bree start
    'startup',
    
    // Run populate cache job every 2 minutes
    { name: 'populate-cache', interval: '2m' },

    // Run clean logs job At 02:17 AM on Sunday (with cron schedule)
    { name: 'clean-logs', cron: '17 2 * * 0' }
  ]
});

bree.start();

All workers located under jobs directory are printing out name and/or a date. The workers files are very simple for presentation purposes.

// jobs/startup.js
console.log('startup job')

// jobs/clean-logs.js
console.log('cleaning logs .... ', new Date())

// jobs/populate-cache
console.log('populating cache .... ', new Date())

And output (using standard console.log) from the above files.

Worker for job "startup" online undefined
Worker for job "populate-cache" online undefined
startup job
populating cache ....  2020-07-20T08:28:41.846Z
Worker for job "startup" exited with code 0 undefined
Worker for job "populate-cache" exited with code 0 undefined
Worker for job "populate-cache" online undefined
populating cache ....  2020-07-20T08:30:41.844Z
Worker for job "populate-cache" exited with code 0 undefined

Great advantage of Bree is ability to implement various interval patters (e.g. every 5 mins, at 10:15 am also at 5:15pm except on Tuesday), which will be very exciting for everyone, who doesn’t necessarily like crontab syntax, especially, sometimes it may be tricky to set it up right. Also, thanks to employment of Node.js workers as a job scheduler environment and jobs stored in separate files, you improve quality of your software by introduction of single-responsibility principle and increase modularity of your application.

Explanation of a cron expression

A cron expression used in the above-mentioned libraries is very similar to crontab with an exception that it also accepts seconds field so you can schedule tasks to the second.

There are online tools that help when constructing your cron expression. One I am using is crontab.guru . But, this tool doesn’t accept the exact same syntax as these libraries, for instance, it doesn’t accept the seconds field.

Cron components with seconds
Cron components with seconds

Seconds

ValueDescription
0 - 59Allowed values - seconds value
*Any value
,Value list separator
-Range values
/Step values

Minutes

ValueDescription
0 - 59Allowed values - minutes value
*Any value
,Value list separator
-Range values
/Step values

Hours

ValueDescription
0 - 23Allowed values - hours value
*Any value
,Value list separator
-Range values
/Step values

Day of month

ValueDescription
0 - 31Allowed values - day of a month number
*Any value
,Value list separator
-Range values
/Step values

Month

ValueDescription
1 - 12Allowed values - month numbers: 1 for January, 2 for February etc.
JAN - DECAllowed values as an alternative to the month numbers
*Any value
,Value list separator
-Range values
/Step values

Day of week

ValueDescription
0 - 6Allowed values - day numbers: 0 for Sunday, 1 for Monday etc.
SUN - SATAllowed values as an alternative to the day numbers
*Any value
,Value list separator
-Range values
/Step values

Some examples of cron schedules:

  • */5 * * * * * - runs every 5 seconds
  • */10 * * * * - runs every 10 minutes
  • 0 6-20/2 * * 1-5 - runs every second hour between 6AM and 8PM on weekdays (Monday through Friday)
  • 5 4 * * SUN - runs at 4:05 on every Sunday

Library testing

As you can see the node-cron library gives lots of flexibility in schedule times creation. However, sometimes it may be tricky to set up the right times. The library also provides a testing mechanism. One of the best ways of checking your schedule expression is to find out a number of the following execution dates. The following example will print out 5 dates of the scheduler expression as follows: 30 9-17/4 * * 1-5.

const cron = require('cron')
const job = cron.job('30 9-17/4 * * 1-5')
console.log(job.nextDates(5).map((date) => date.toString()))

That gives the following output (the example code run at 16:48 on 12 Feb 2019)

;[
  'Tue Feb 12 2019 17:30:00 GMT+0000',
  'Wed Feb 13 2019 09:30:00 GMT+0000',
  'Wed Feb 13 2019 13:30:00 GMT+0000',
  'Wed Feb 13 2019 17:30:00 GMT+0000',
  'Thu Feb 14 2019 09:30:00 GMT+0000',
]

Test of this cron expression: 5 4 * * SUN give the following output:

;[
  'Sun Feb 17 2019 04:05:00 GMT+0000',
  'Sun Feb 24 2019 04:05:00 GMT+0000',
  'Sun Mar 03 2019 04:05:00 GMT+0000',
  'Sun Mar 10 2019 04:05:00 GMT+0000',
  'Sun Mar 17 2019 04:05:00 GMT+0000',
]

Happy coding!