DEV Community

Cover image for A guide to Deno.cron
Megan Lee for LogRocket

Posted on • Originally published at blog.logrocket.com

A guide to Deno.cron

Written by Rosario De Chiara✏️

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.”

The cron string

The 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:

Scheduling Parameters

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"))
Enter fullscreen mode Exit fullscreen mode

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
        }
    }      
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/sleep@v1.3.0/mod.ts"
import { time } from "https://denopkg.com/burhanahmeed/time.ts@v2.0.1/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")
    })
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/time.ts@v2.0.1/mod.ts";

Deno.cron("task1", { minute: { every: 1 }}, {
    backoffSchedule: [1000, 5000, 10000],
}, () => {
    console.log('Time now UTC: ', time().t)

    throw new Error();
});
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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).

Running in the cloud

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.

Conclusion

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.


Get set up with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.

NPM:

$ npm i --save logrocket 

// Code:

import LogRocket from 'logrocket'; 
LogRocket.init('app/id');
Enter fullscreen mode Exit fullscreen mode

Script Tag:

Add to your HTML:

<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
Enter fullscreen mode Exit fullscreen mode

3.(Optional) Install plugins for deeper integrations with your stack:

  • Redux middleware
  • ngrx middleware
  • Vuex plugin

Get started now

Top comments (0)