NestJS 定時任務(wù)

2023-09-08 17:50 更新

定時任務(wù)允許你按照指定的日期/時間、一定時間間隔或者一定時間后單次執(zhí)行來調(diào)度(scheduling)任意代碼(方法/函數(shù))。在Linux世界中,這經(jīng)常通過操作系統(tǒng)層面的cron包等執(zhí)行。在Node.js應(yīng)用中,有幾個不同的包可以模擬 cron 包的功能。Nest 提供了@nestjs/schedule包,其集成了流行的 Node.js 的node-cron包,我們將在本章中應(yīng)用該包。

安裝

我們首先從安裝需要的依賴開始。

$ npm install --save @nestjs/schedule

要激活工作調(diào)度,從根AppModule中導(dǎo)入ScheduleModule并運行forRoot()靜態(tài)方法,如下:

app.module.ts
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';

@Module({
  imports: [ScheduleModule.forRoot()],
})
export class AppModule {}

.forRoot()調(diào)用初始化調(diào)度器并且注冊在你應(yīng)用中任何聲明的cron jobs,timeouts和intervals。注冊開始于onApplicationBootstrap生命周期鉤子發(fā)生時,保證所有模塊都已經(jīng)載入,任何計劃工作已經(jīng)聲明。

聲明計時工作(cron job)

一個計時工作調(diào)度任何函數(shù)(方法調(diào)用)以自動運行, 計時工作可以:

  • 單次,在指定日期/時間
  • 重復(fù)循環(huán):重復(fù)工作可以在指定周期中指定執(zhí)行(例如,每小時,每周,或者每 5 分鐘)

在包含要運行代碼的方法定義前使用@Cron()裝飾器聲明一個計時工作,如下:

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('Called when the current second is 45');
  }
}

在這個例子中,handleCron()方法將在當(dāng)前時間為45秒時定期執(zhí)行。換句話說,該方法每分鐘執(zhí)行一次,在第 45 秒執(zhí)行。

@Cron()裝飾器支持標(biāo)準(zhǔn)的cron patterns:

  • 星號通配符 (也就是 *)
  • 范圍(也就是 1-3,5)
  • 步長(也就是 */2)

在上述例子中,我們給裝飾器傳遞了45 * * * * *,下列鍵展示了每個位置的計時模式字符串的意義:

* * * * * *
| | | | | |
| | | | | day of week
| | | | month
| | | day of month
| | hour
| minute
second (optional)

一些示例的計時模式包括:

名稱含義
* * * * * *每秒
45 * * * * *每分鐘第 45 秒
_ 10 _ * * *每小時,從第 10 分鐘開始
0 _/30 9-17 _ * *上午 9 點到下午 5 點之間每 30 分鐘
0 30 11 * * 1-5周一至周五上午 11:30

@nestjs/schedule包提供一個方便的枚舉

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_45_SECONDS)
  handleCron() {
    this.logger.debug('Called every 45 seconds');
  }
}

在本例中,handleCron()方法每45秒執(zhí)行一次。

可選地,你可以為將一個JavaScript的Date對象傳遞給@Cron()裝飾器。這樣做可以讓工作在指定日期執(zhí)行一次。

使用JavaScript日期算法來關(guān)聯(lián)當(dāng)前日期和計劃工作。@Cron(new Date(Date.now()+10*1000))用于在應(yīng)用啟動 10 秒后運行。

你可以在聲明后訪問并控制一個定時任務(wù),或者使用動態(tài) API動態(tài)創(chuàng)建一個定時任務(wù)(其定時模式在運行時定義)。要通過 API 聲明定時任務(wù),你必須通過將選項對象中的name屬性作為可選的第二個參數(shù)傳遞給裝飾器,從而將工作和名稱聯(lián)系起來。

@Cron('* * 8 * * *', {
  name: 'notifications',
})
triggerNotifications() {}

聲明間隔

要聲明一個以一定間隔運行的方法,使用@Interval()裝飾器前綴。以毫秒單位的number傳遞間隔值,如下:

@Interval(10000)
handleInterval() {
  this.logger.debug('Called every 10 seconds');
}

本機制在底層使用JavaScript的setInterval()函數(shù)。你也可以使用定期調(diào)度工作來應(yīng)用一個定時任務(wù)。

如果你希望在聲明類之外通過動態(tài) API控制你聲明的時間間隔。使用下列結(jié)構(gòu)將名稱與間隔關(guān)聯(lián)起來。

@Interval('notifications', 2500)
handleInterval() {}

動態(tài) API 也支持動態(tài)創(chuàng)建時間間隔,間隔屬性在運行時定義,可以列出和刪除他們。

聲明延時任務(wù)

要聲明一個在指定時間后運行(一次)的方法,使用@Timeout()裝飾器前綴。將從應(yīng)用啟動的相關(guān)時間偏移量(毫秒)傳遞給裝飾器,如下:

@Timeout(5000)
handleTimeout() {
  this.logger.debug('Called once after 5 seconds');
}

本機制在底層使用 JavaScript 的setTimeout()方法

如果你想要在聲明類之外通過動態(tài) API 控制你聲明的超時時間,將超時時間和一個名稱以如下結(jié)構(gòu)關(guān)聯(lián):

@Timeout('notifications', 2500)
handleTimeout() {}

動態(tài) API 同時支持創(chuàng)建動態(tài)超時時間,超時時間在運行時定義,可以列舉和刪除他們。

動態(tài)規(guī)劃模塊 API

@nestjs/schedule模塊提供了一個支持管理聲明定時、超時和間隔任務(wù)的動態(tài) API。該 API 也支持創(chuàng)建和管理動態(tài)定時、超時和間隔,這些屬性在運行時定義。

動態(tài)定時任務(wù)

使用SchedulerRegistryAPI 從你代碼的任何地方獲取一個CronJob實例的引用。首先,使用標(biāo)準(zhǔn)構(gòu)造器注入ScheduleRegistry。

constructor(private schedulerRegistry: SchedulerRegistry) {}

從@nestjs/schedule包導(dǎo)入SchedulerRegistry。

使用下列類,假設(shè)通過下列定義聲明一個定時任務(wù):

@Cron('* * 8 * * *', {
  name: 'notifications',
})
triggerNotifications() {}

如下獲取本工作:

const job = this.schedulerRegistry.getCronJob('notifications');

job.stop();
console.log(job.lastDate());

getCronJob()方法返回一個命名的定時任務(wù)。然后返回一個包含下列方法的CronJob對象:

  • stop()-停止一個按調(diào)度運行的任務(wù)
  • start()-重啟一個停止的任務(wù)
  • setTime(time:CronTime)-停止一個任務(wù),為它設(shè)置一個新的時間,然后再啟動它
  • lastDate()-返回一個表示工作最后執(zhí)行日期的字符串
  • nextDates(count:number)-返回一個moment對象的數(shù)組(大小count),代表即將執(zhí)行的任務(wù)日期

在moment對象中使用toDate()來渲染成易讀的形式。

使用SchedulerRegistry.addCronJob()動態(tài)創(chuàng)建一個新的定時任務(wù),如下:

addCronJob(name: string, seconds: string) {
  const job = new CronJob(`${seconds} * * * * *`, () => {
    this.logger.warn(`time (${seconds}) for job ${name} to run!`);
  });

  this.scheduler.addCronJob(name, job);
  job.start();

  this.logger.warn(
    `job ${name} added for each minute at ${seconds} seconds!`,
  );
}

在這個代碼中,我們使用cron包中的CronJob對象來創(chuàng)建定時任務(wù)。CronJob構(gòu)造器采用一個定時模式(類似@Cron()裝飾器作為其第一個參數(shù),以及一個將執(zhí)行的回調(diào)函數(shù)作為其第二個參數(shù)。SchedulerRegistry.addCronJob()方法有兩個參數(shù):一個CronJob名稱,以及一個CronJob對象自身。

記得在使用前注入SchedulerRegistry,從cron包中導(dǎo)入 CronJob。

使用SchedulerRegistry.deleteCronJob()方法刪除一個命名的定時任務(wù),如下:

deleteCron(name: string) {
  this.scheduler.deleteCronJob(name);
  this.logger.warn(`job ${name} deleted!`);
}

使用SchedulerRegistry.getCronJobs()方法列出所有定時任務(wù),如下:

getCrons() {
  const jobs = this.scheduler.getCronJobs();
  jobs.forEach((value, key, map) => {
    let next;
    try {
      next = value.nextDates().toDate();
    } catch (e) {
      next = 'error: next fire date is in the past!';
    }
    this.logger.log(`job: ${key} -> next: ${next}`);
  });
}

getCronJobs()方法返回一個map。在這個代碼中,我們遍歷該map并且嘗試獲取每個CronJob的nextDates()方法。在CronJobAPI 中,如果一個工作已經(jīng)執(zhí)行了并且沒有下一次執(zhí)行的日期,將拋出異常。

動態(tài)間隔

使用SchedulerRegistry.getInterval()方法獲取一個時間間隔的引用。如上,使用標(biāo)準(zhǔn)構(gòu)造注入SchedulerRegistry。

constructor(private schedulerRegistry: SchedulerRegistry) {}

如下使用:

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

使用SchedulerRegistry.addInterval() 方法創(chuàng)建一個新的動態(tài)間隔,如下:

addInterval(name: string, seconds: string) {
  const callback = () => {
    this.logger.warn(`Interval ${name} executing at time (${seconds})!`);
  };

  const interval = setInterval(callback, seconds);
  this.scheduler.addInterval(name, interval);
}

在該代碼中,我們創(chuàng)建了一個標(biāo)準(zhǔn)的 JavaScript 間隔,然后將其傳遞給ScheduleRegistry.addInterval()方法。該方法包括兩個參數(shù):一個時間間隔的名稱,和時間間隔本身。

如下使用SchedulerRegistry.deleteInterval()刪除一個命名的時間間隔:

deleteInterval(name: string) {
  this.scheduler.deleteInterval(name);
  this.logger.warn(`Interval ${name} deleted!`);
}

使用SchedulerRegistry.getIntervals()方法如下列出所有的時間間隔:

getIntervals() {
  const intervals = this.scheduler.getIntervals();
  intervals.forEach(key => this.logger.log(`Interval: ${key}`));
}

動態(tài)超時

使用SchedulerRegistry.getTimeout()方法獲取一個超時引用,如上,使用標(biāo)準(zhǔn)構(gòu)造注入SchedulerRegistry:

constructor(private schedulerRegistry: SchedulerRegistry) {}

并如下使用:

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

使用SchedulerRegistry.addTimeout()方法創(chuàng)建一個新的動態(tài)超時,如下:

addTimeout(name: string, seconds: string) {
  const callback = () => {
    this.logger.warn(`Timeout ${name} executing after (${seconds})!`);
  });

  const timeout = setTimeout(callback, seconds);
  this.scheduler.addTimeout(name, timeout);
}

在該代碼中,我們創(chuàng)建了個一個標(biāo)準(zhǔn)的 JavaScript 超時任務(wù),然后將其傳遞給ScheduleRegistry.addTimeout()方法,該方法包含兩個參數(shù):一個超時的名稱,以及超時對象自身。

使用SchedulerRegistry.deleteTimeout()方法刪除一個命名的超時,如下:

deleteTimeout(name: string) {
  this.scheduler.deleteTimeout(name);
  this.logger.warn(`Timeout ${name} deleted!`);
}

使用SchedulerRegistry.getTimeouts()方法列出所有超時任務(wù):

getTimeouts() {
  const timeouts = this.scheduler.getTimeouts();
  timeouts.forEach(key => this.logger.log(`Timeout: ${key}`));
}

示例

一個可用的例子見這里。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號