Passer au contenu

Planification des tâches

La planification des tâches vous permet de programmer du code arbitraire (méthodes/fonctions) pour s’exécuter à une date/heure fixe, à intervalles réguliers, ou une fois après un intervalle spécifié. Dans le monde Linux, cela est souvent géré par des paquets comme cron au niveau du système d’exploitation. Pour les applications Node.js, plusieurs paquets imitent des fonctionnalités similaires à cron. Nest fournit le paquet @nestjs/schedule, qui s’intègre au célèbre paquet Node.js cron. Nous couvrirons ce paquet dans le chapitre actuel.

Installation

Pour commencer à l’utiliser, nous installons d’abord les dépendances requises.

Fenêtre de terminal
$ npm install --save @nestjs/schedule

Pour activer la planification des tâches, importez le ScheduleModule dans le module racine AppModule et exécutez la méthode statique forRoot() comme indiqué ci-dessous :

app.module.ts
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
@Module({
imports: [
ScheduleModule.forRoot()
],
})
export class AppModule {}

L’appel forRoot() initialise le planificateur et enregistre toute déclaration de cron jobs, timeouts et intervals qui existent dans votre application. L’enregistrement se produit lors de l’appel du hook de cycle de vie onApplicationBootstrap, assurant que tous les modules ont été chargés et ont déclaré les travaux planifiés.

Cron jobs déclaratifs

Un cron job programme une fonction arbitraire (appel de méthode) pour s’exécuter automatiquement. Les cron jobs peuvent s’exécuter :

  • Une fois, à une date/heure spécifiée.
  • De manière récurrente ; les travaux récurrents peuvent s’exécuter à un instant spécifié dans un intervalle spécifié (par exemple, une fois par heure, une fois par semaine, une fois toutes les 5 minutes).

Déclarez un cron job avec le décorateur @Cron() précédant la définition de méthode contenant le code à exécuter, comme suit :

tasks.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Cron('45 * * * * *')
handleCron() {
this.logger.debug('Appelé lorsque la seconde actuelle est 45');
}
}

Dans cet exemple, la méthode handleCron() sera appelée chaque fois que la seconde actuelle est 45. En d’autres termes, la méthode sera exécutée une fois par minute, à la marque de 45 secondes.

Le décorateur @Cron() prend en charge les modèles cron standards suivants :

  • Asterisk (par exemple, *)
  • Ranges (par exemple, 1-3,5)
  • Steps (par exemple, */2)

Dans l’exemple ci-dessus, nous avons passé 45 * * * * * au décorateur. La clé suivante montre comment chaque position dans la chaîne de modèle cron est interprétée :

* * * * * *
| | | | | |
| | | | | | day of week
| | | | months
| | | day of month
| | hours
| minutes
| seconds (facultatif)

Voici quelques exemples de modèles cron :

ModèleDescription
* * * * * *chaque seconde
45 * * * * *chaque minute, à la 45ème seconde
0 10 * * * *chaque heure, au début de la 10ème minute
0 */30 9-17 * * *toutes les 30 minutes entre 9h et 17h
0 30 11 * * 1-5Du lundi au vendredi à 11h30

Le paquet @nestjs/schedule fournit une énumération pratique avec des modèles cron couramment utilisés. Vous pouvez utiliser cette énumération comme suit :

tasks.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Cron(CronExpression.EVERY_30_SECONDS)
handleCron() {
this.logger.debug('Appelé toutes les 30 secondes');
}
}

Dans cet exemple, la méthode handleCron() sera appelée toutes les 30 secondes.

Alternativement, vous pouvez fournir un objet JavaScript Date au décorateur @Cron(). Cela provoque l’exécution du travail exactement une fois, à la date spécifiée.

Vous pouvez également fournir des options supplémentaires en tant que deuxième paramètre au décorateur @Cron().

OptionDescription
nameUtile pour accéder et contrôler un cron job après qu’il a été déclaré.
timeZoneSpécifiez le fuseau horaire pour l’exécution. Cela modifiera l’heure réelle par rapport à votre fuseau horaire. Si le fuseau horaire est invalide, une erreur est levée. Vous pouvez vérifier tous les fuseaux horaires disponibles sur le site Moment Timezone.
utcOffsetCela vous permet de spécifier le décalage de votre fuseau horaire plutôt que d’utiliser le paramètre timeZone.
disabledIndique si le travail sera exécuté ou non.
notification.service.ts
import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class NotificationService {
@Cron('* * 0 * * *', {
name: 'notifications',
timeZone: 'Europe/Paris',
})
triggerNotifications() {
// Logic to trigger notifications
}
}

Vous pouvez accéder et contrôler un cron job après qu’il a été déclaré, ou créer dynamiquement un cron job (où son modèle cron est défini à l’exécution) avec l’API dynamique. Pour accéder à un cron job déclaratif via l’API, vous devez associer le travail avec un nom en passant la propriété name dans un objet d’options facultatif en tant que deuxième argument du décorateur.

Intervalles déclaratifs

Pour déclarer qu’une méthode doit s’exécuter à un intervalle (récurrent) spécifié, préfixez la définition de la méthode avec le décorateur @Interval(). Passez la valeur de l’intervalle, en tant que nombre en millisecondes, au décorateur comme indiqué ci-dessous :

tasks.service.ts
import { Injectable } from '@nestjs/common';
import { Interval } from '@nestjs/schedule';
@Injectable()
export class TasksService {
@Interval(10000)
handleInterval() {
// Logic to execute every 10 seconds
}
}

Si vous souhaitez contrôler votre intervalle déclaratif depuis l’extérieur de la classe déclarative via l’API dynamique, associez l’intervalle avec un nom en utilisant la construction suivante :

tasks.service.ts
import { Injectable } from '@nestjs/common';
import { Interval } from '@nestjs/schedule';
@Injectable()
export class TasksService {
@Interval('notifications', 2500)
handleInterval() {
// Logic to execute
}
}

L’API dynamique permet également de créer des intervalles dynamiques, où les propriétés de l’intervalle sont définies à l’exécution, et de lister et supprimer ces intervalles.

API dynamique du module de planification

Le module @nestjs/schedule fournit une API dynamique qui permet de gérer les cron jobs déclaratifs, les timeouts déclaratifs et les intervalles déclaratifs. L’API permet également de créer et gérer des cron jobs, timeouts et intervalles dynamiques, où les propriétés sont définies à l’exécution.

Cron jobs dynamiques

Obtenez une référence à une instance CronJob par nom depuis n’importe où dans votre code en utilisant l’API SchedulerRegistry. Tout d’abord, injectez SchedulerRegistry à l’aide de l’injection de constructeur standard :

app.module.ts
import { SchedulerRegistry } from '@nestjs/schedule';
constructor(private schedulerRegistry: SchedulerRegistry) {}

Ensuite, utilisez-le dans une classe comme suit. Supposons qu’un cron job ait été créé avec la déclaration suivante :

notification.service.ts
@Cron('* * 8 * * *', {
name: 'notifications',
})
triggerNotifications() {
// Logic to trigger notifications
}

Accédez à ce job en utilisant :

example.service.ts
const job = this.schedulerRegistry.getCronJob('notifications');
job.stop();
console.log(job.lastDate());

La méthode getCronJob() retourne le cron job nommé. L’objet CronJob retourné possède les méthodes suivantes :

  • stop() - arrête un travail prévu pour s’exécuter.
  • start() - redémarre un travail qui a été arrêté.
  • setTime(time: CronTime) - arrête un travail, fixe un nouveau temps pour celui-ci, puis le redémarre.
  • lastDate() - retourne une représentation DateTime de la date à laquelle la dernière exécution d’un travail a eu lieu.
  • nextDate() - retourne une représentation DateTime de la date à laquelle la prochaine exécution d’un travail est prévue.
  • nextDates(count: number) - fournit un tableau (taille count) de représentations DateTime pour le prochain ensemble de dates qui déclencheront l’exécution du travail. count par défaut est 0, renvoyant un tableau vide.

Créer un nouveau cron job de manière dynamique en utilisant la méthode SchedulerRegistry#addCronJob, comme suit :

example.service.ts
addCronJob(name: string, seconds: string) {
const job = new CronJob(`\${seconds} * * * * *`, () => {
this.logger.warn(`time (\${seconds}) for job \${name} to run!`);
});
this.schedulerRegistry.addCronJob(name, job);
job.start();
this.logger.warn(`job \${name} added for each minute at \${seconds} seconds!`);
}

Dans ce code, nous utilisons l’objet CronJob du paquet cron pour créer le cron job. Le constructeur CronJob prend un modèle cron (tout comme le décorateur @Cron()) comme premier argument, et un rappel à exécuter lorsque le timer cron se déclenche comme second argument. La méthode SchedulerRegistry#addCronJob prend deux arguments : un nom pour le CronJob, et l’objet CronJob lui-même.

Supprimez un cron job nommé en utilisant la méthode SchedulerRegistry#deleteCronJob, comme suit :

example.service.ts
deleteCron(name: string) {
this.schedulerRegistry.deleteCronJob(name);
this.logger.warn(`job \${name} deleted!`);
}

Listez tous les cron jobs en utilisant la méthode SchedulerRegistry#getCronJobs comme suit :

example.service.ts
getCrons() {
const jobs = this.schedulerRegistry.getCronJobs();
jobs.forEach((value, key, map) => {
let next;
try {
next = value.nextDate().toJSDate();
} catch (e) {
next = 'error: next fire date is in the past!';
}
this.logger.log(`job: \${key} -> next: \${next}`);
});
}

La méthode getCronJobs() retourne une map. Dans ce code, nous parcourons la carte et tentons d’accéder à la méthode nextDate() de chaque CronJob. Dans l’API CronJob, si un travail a déjà été exécuté et n’a aucune date d’exécution future, cela lève une exception.

Intervalles dynamiques

Obtenez une référence à un intervalle avec la méthode SchedulerRegistry#getInterval. Comme précédemment, injectez SchedulerRegistry en utilisant l’injection de constructeur standard :

example.service.ts
constructor(private schedulerRegistry: SchedulerRegistry) {}

Et utilisez-le comme suit :

example.service.ts
const interval = this.schedulerRegistry.getInterval('notifications');
clearInterval(interval);

Créer un nouvel intervalle de manière dynamique en utilisant la méthode SchedulerRegistry#addInterval, comme suit :

example.service.ts
addInterval(name: string, milliseconds: number) {
const callback = () => {
this.logger.warn(`Interval \${name} executing at time (\${milliseconds})!`);
};
const interval = setInterval(callback, milliseconds);
this.schedulerRegistry.addInterval(name, interval);
}

Dans ce code, nous créons un intervalle JavaScript standard, puis le passons à la méthode SchedulerRegistry#addInterval. Cette méthode prend deux arguments : un nom pour l’intervalle et l’intervalle lui-même.

Supprimez un intervalle nommé en utilisant la méthode SchedulerRegistry#deleteInterval, comme suit :

example.service.ts
deleteInterval(name: string) {
this.schedulerRegistry.deleteInterval(name);
this.logger.warn(`Interval \${name} deleted!`);
}

Listez tous les intervalles en utilisant la méthode SchedulerRegistry#getIntervals comme suit :

example.service.ts
getIntervals() {
const intervals = this.schedulerRegistry.getIntervals();
intervals.forEach(key => {
this.logger.log(`Interval: \${key}`);
});
}

Timeouts dynamiques

Obtenez une référence à un timeout avec la méthode SchedulerRegistry#getTimeout. Comme précédemment, injectez SchedulerRegistry en utilisant l’injection de constructeur standard :

example.service.ts
constructor(private schedulerRegistry: SchedulerRegistry) {}

Et utilisez-le comme suit :

example.service.ts
const timeout = this.schedulerRegistry.getTimeout('notifications');
clearTimeout(timeout);

Créer un nouveau timeout de manière dynamique en utilisant la méthode SchedulerRegistry#addTimeout, comme suit :

example.service.ts
addTimeout(name: string, milliseconds: number) {
const callback = () => {
this.logger.warn(`Timeout \${name} executing after (\${milliseconds})!`);
};
const timeout = setTimeout(callback, milliseconds);
this.schedulerRegistry.addTimeout(name, timeout);
}

Dans ce code, nous créons un timeout JavaScript standard, puis le passons à la méthode SchedulerRegistry#addTimeout. Cette méthode prend deux arguments : un nom pour le timeout et le timeout lui-même.

Supprimez un timeout nommé en utilisant la méthode SchedulerRegistry#deleteTimeout, comme suit :

example.service.ts
deleteTimeout(name: string) {
this.schedulerRegistry.deleteTimeout(name);
this.logger.warn(`Timeout \${name} deleted!`);
}

Listez tous les timeouts en utilisant la méthode SchedulerRegistry#getTimeouts comme suit :

example.service.ts
getTimeouts() {
const timeouts = this.schedulerRegistry.getTimeouts();
timeouts.forEach(key => {
this.logger.log(`Timeout: \${key}`);
});
}

Exemple

Un exemple fonctionnel est disponible ici.