Files
What you'll learn
- Register ScheduleModule and schedule methods with @Cron, @Interval and @Timeout
- Read a cron pattern and use CronExpression and the options that matter (name, timeZone, waitForCompletion)
- Stop, start, add and delete named tasks at runtime through the SchedulerRegistry
Task Scheduling
Some work is not a response to a request. The cats are fed at fixed times whoever is on the website; a report goes out every night; a cleanup runs on the hour. Node has setInterval and setTimeout, and cron, the Unix scheduler, has a little language for "every weekday at 11:30". @nestjs/schedule brings both into a Nest provider as decorators on methods, so a scheduled task is written like any other method of a service and has the service's dependencies, and gives you a registry to start and stop tasks at runtime.
ScheduleModule
Register it once, in the root module:
@Module({
imports: [ScheduleModule.forRoot()],
})
export class AppModule {}
At startup Nest scans every provider for scheduled methods and registers them. Nothing runs before the application has finished booting.
Cron jobs
@Cron() takes a cron pattern, six fields with an optional seconds field first:
* * * * * *
| | | | | |
| | | | | day of week
| | | | months
| | | day of month
| | hours
| minutes
seconds (optional)
@Cron('45 * * * * *') runs when the seconds hand hits 45, every minute; 0 30 11 * * 1-5 is 11:30 on weekdays; 0 */30 9-17 * * * every half hour during office hours. CronExpression names the common ones, CronExpression.EVERY_30_SECONDS, EVERY_DAY_AT_MIDNIGHT, so nobody has to count asterisks. A Date runs the method once at that moment. The options object takes name, for the registry; timeZone, since a shelter in Madrid and a server in Virginia disagree about 11:30; waitForCompletion, to skip a tick while the previous one is still running; and disabled.
Intervals and timeouts
@Interval(10000)
handleInterval() {
this.logger.debug('Called every 10 seconds');
}
@Timeout(5000)
handleTimeout() {
this.logger.debug('Called once after 5 seconds');
}
@Interval is setInterval; @Timeout is setTimeout, once after boot, for warm-ups and delayed starts. Both take an optional name as their first argument, @Interval('notifications', 2500), which is how the registry finds them later.
The registry
SchedulerRegistry is the runtime side, injected like any provider. getCronJob('name') returns the job, with stop(), start(), lastDate(), nextDate() and setTime(); addCronJob(name, new CronJob(pattern, callback)) registers one built at runtime with the cron package's class; deleteCronJob(name) removes it. Intervals and timeouts have the same trio: getInterval, addInterval(name, setInterval(...)), deleteInterval, and getTimeout, addTimeout, deleteTimeout. doesExist('interval', name) asks before touching. Deleting an interval clears it; adding one registers a handle you created, so the registry can clear it when the application shuts down.
Your task
The cats are fed on a clock, and the staff can pause it.
AppModuleturns scheduling on.FeedingService: a feeding round every 200 ms as a named intervalfeeding; the shelter opens once, 100 ms after boot; a report every second on the cron clock.pause()deletes thefeedinginterval through the registry;resume()registers a fresh one under the same name, calling the same round.
The graded requests wait for the clock: the status is read after enough time for rounds and a report to have run, and again after a pause. Run and watch the console: the rounds and the report interleave.
When it fails
roundsstays at 0:ScheduleModule.forRoot()is missing, so no decorator was registered; the methods exist and nobody calls them.Nest can't resolve dependencies of the FeedingService (CatsService, ?), argumentSchedulerRegistry: same cause; the registry is provided by the module.No Interval was found with the given name (feeding):deleteIntervalon a name that does not exist; the interval was never named, or was already paused. AskdoesExistfirst.- Rounds keep counting while paused:
pause()set a flag but the interval still runs; the registry, not the flag, stops it.
Remember
ScheduleModule.forRoot()once;@Cron,@Intervaland@Timeouton provider methods.- A cron pattern is six fields, seconds first;
CronExpressionnames the usual ones;timeZonematters. - Name a task to reach it later;
SchedulerRegistrystarts, stops, adds and deletes by name. - A scheduled method has its service's dependencies, so a task can do real work.
Stuck? Show a hint
app.module.ts: ScheduleModule.forRoot() from '@nestjs/schedule'. feeding.service.ts: @Interval('feeding', 200) on feedingRound, @Timeout(100) on openShelter, @Cron(CronExpression.EVERY_SECOND) on report; inject SchedulerRegistry; pause(): if (this.registry.doesExist('interval', 'feeding')) this.registry.deleteInterval('feeding'); resume(): this.registry.addInterval('feeding', setInterval(() => void this.feedingRound(), 200)).
Press Run tests to start the app. Its log appears here.Graded endpoints
A cat to feed
Read 1.1 s later: the timeout opened the shelter, the interval has fed several times, and the cron job has reported at least once (it fires on the second)
Each round fed every cat; the count is on the row
The named interval is deleted through the registry
Read 500 ms after pausing: no round ran in between
A fresh interval registered under the same name
Read 500 ms later: rounds are counting again