In the UNIX world, one of the oldest and most reliable mechanisms available since the late 70s has been the cron
command. cron
allows you to schedule the execution of tasks by specifying a simple (and sometimes cryptic) string to state when and how frequently a given command should be executed.
In the heyday of UNIX, these strings, for every specific command, were collected in the crontab
file. Now, even within a vastly different technological framework, the cron
mechanism is still relevant. In this article, we will see how to use the cron
package in Deno. The code we present here is available in this GitHub repository.
We will assume that you have never used Deno before, so the first step is to install it. It is just a matter of executing the installation shell script; for the proper way of doing so, follow the official documentation. To get acquainted with Deno, check out “Getting started with Deno and Fresh.”
cron
stringThe crontab
string used by Deno.cron
follows a specific format consisting of five fields, each representing different time parameters for scheduling tasks. The basic format includes a minute, hour, day of the month, month, and day of the week field:
These fields are separated by one space, and numbers or special characters can be used to specify time intervals or specific points in time. This format allows users to define precise and recurring schedules for automated tasks.
Here’s a simple example of a schedule using Deno.cron
:
30 2 * * 1-5 30 2 * * 1-5
In this example, 30
specifies the minute when the task will be executed (in this case, 30 minutes past the hour). 2
represents the hour of the day when the task will be executed (2 AM). *
stands for “every day of the month,” meaning the task will occur on any day of the month. The next *
indicates “every month,” implying the task is scheduled for every month. And 1-5
refers to “Monday to Friday” for the day of the week, indicating that the task will run from Monday to Friday.
In summary, this crontab
string schedules a task to run at 2:30 AM from Monday to Friday. The good news is that the general idea of the crontab
string from the late 70s has been translated to modern approaches, and it is possible to define a string by using a more expressive JavaScript object. We’ll explore this in the next section.
The Cron library is available in Deno (v1.43 at the time of writing) but is tagged as unstable. This means that when you want to execute the code in the next examples, you must use this to explicitly enable the use of the (still) unstable cron
API:
$ deno run --unstable-cron server.ts
The first example is pretty simple:
Deno.cron("task1", "* * * * *", () => console.log("This will print every ONE minute")) Deno.cron("task2", { minute: { every: 2 } }, () => console.log("This will print every TWO minutes"))
In the code, we defined two tasks with different schedules: the first one ( 'task1'
) is scheduled to run every minute because of the crontab
string "* * * * *"
. The second activity, named task2
, is scheduled every two minutes by the JSON object:
{ minute: { every: 2 } }
You can probably agree that this is much more expressive than the crontab
string.
As expected, the execution prints the message from task1
every minute, and the message from task2
every two executions of task1
:
$ deno run --unstable-cron .\example1.ts This will print every ONE minute This will print every TWO minutes This will print every ONE minute This will print every ONE minute This will print every TWO minutes
In the first example, we saw how to schedule two pretty simple tasks. However, this mechanism has a limitation: the execution of the function associated with a task cannot overlap the execution of another task. If this is the case, Deno will skip the execution of the second task.
This mechanism is important to understand because if the time required to complete a task’s associated function is uncertain, the order in which tasks are executed will also be unpredictable because the execution cannot overlap and if a function is in execution, it blocks the execution of every other function. In the example02.ts
file, here is a basic example to show this effect:
import { sleep } from "https://deno.land/x/[email protected]/mod.ts" import { time } from "https://denopkg.com/burhanahmeed/[email protected]/mod.ts"; Deno.cron("task1", { minute: { every: 1 } }, () => { console.log('Time now UTC: ', time().t) }) Deno.cron("task2", { minute: { every: 2 } }, () => { console.log("Sleeping 3 minutes") sleep(3 * 60) console.log("Done") })
In this example, we will use two new Deno packages: time
, to obtain the current time and sleep
, to let a task waste some time in order to overlap the execution of the two tasks.
In the following image, you can see an execution and you may notice how the scheduled task that prints the time (task1
in the source) skipped its execution: there is a jump from minute 26 to minute 31. This is more than the three minutes we let task2
“sleep” and this clarifies how unreliable the scheduling of tasks whose executions may overlap can be:
$ deno run --unstable-cron .\example2.ts Time now UTC: 2024-03-17T16:24:00.009Z Time now UTC: 2024-03-17T16:25:00.001Z Sleeping 3 minutes Done Time now UTC: 2024-03-17T16:26:00.010Z Time now UTC: 2024-03-17T16:31:30.309Z Sleeping 3 minutes Done Time now UTC: 2024-03-17T16:32:00.007Z Sleeping 3 minutes Done
The last example is useful in handling the failure of a recurring task. The general idea is to schedule a task in the usual way, as we have seen above, together with a particular schedule, named backoffSchedule
, to be used in case of an error.
The situation intended to be modeled here is as follows: schedule an invocation to a service, at a given pace. For our example, we’ll use five minutes. In case the invocation throws an error, keep retrying the invocation with a backoff time, so as to not clog the service with too many attempts:
import { time } from "https://denopkg.com/burhanahmeed/[email protected]/mod.ts"; Deno.cron("task1", { minute: { every: 1 }}, { backoffSchedule: [1000, 5000, 10000], }, () => { console.log('Time now UTC: ', time().t) throw new Error(); });
Unsuccessful cron executions are automatically attempted again according to a default retry strategy. To define this strategy, you can utilize the backoffSchedule
attribute to specify an array of time intervals (in milliseconds) to wait before reattempting the function call.
In the example above, we define a backoff schedule of one second, five seconds, and 10 seconds, and, to show how the backoff mechanism works, the function associated with task1
will simply throw an error:
Time now UTC: 2024-03-17T18:40:00.009Z Exception in cron handler task1 Error at file:///example3.ts:8:11 at ext:deno_cron/01_cron.ts:101:24 at eventLoopTick (ext:core/01_core.js:169:7) Time now UTC: 2024-03-17T18:40:01.029Z Exception in cron handler task1 Error at file:///example3.ts:8:11 at ext:deno_cron/01_cron.ts:101:24 at eventLoopTick (ext:core/01_core.js:169:7) Time now UTC: 2024-03-17T18:40:06.037Z Exception in cron handler task1 Error at file:///example3.ts:8:11 at ext:deno_cron/01_cron.ts:101:24 at eventLoopTick (ext:core/01_core.js:169:7) Time now UTC: 2024-03-17T18:40:16.050Z Exception in cron handler task1 Error at file:///example3.ts:8:11 at ext:deno_cron/01_cron.ts:101:24 at eventLoopTick (ext:core/01_core.js:169:7)
In the execution, you can notice that, after the first thrown exception (time 18:40:00
), we can see another exception after one second (time 18:40:01), five seconds (time 18:40:06), and 10 seconds (time 18:40:16).
The real use of Deno.cron
is on the server side of your application, to re-calculate data in a table, retry API calls, update dashboards, and so on. In the Deno ecosystem, this is done by leveraging Deno Deploy, which is a globally distributed platform for serverless JavaScript applications.
The implementation of Deno.cron
works slightly differently on Deno Deploy. In particular, Deno Deploy assures that cron tasks are executed at least once per each scheduled time interval. This generally means that your cron handler will be invoked once per scheduled time but, in the presence of failures, the handler may be invoked multiple times for the same scheduled time.
In summary, this article introduced the concept of scheduling tasks using Deno.cron
, covering examples of various scheduling formats. We saw how important it is to account for non-deterministic task execution due to potentially overlapping functions, and offered practical examples for Deno.cron
’s capabilities in handling scheduled tasks.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]