UNPKG

@eggjs/schedule

Version:

schedule plugin for egg, support corn job.

108 lines (92 loc) 3.3 kB
import assert from 'node:assert'; import type { CronExpression } from 'cron-parser'; import cronParser from 'cron-parser'; import { ms } from 'humanize-ms'; import safeTimers from 'safe-timers'; import { logDate } from 'utility'; import type { Agent } from 'egg'; import type { EggScheduleConfig } from '../types.js'; import { BaseStrategy } from './base.js'; export abstract class TimerStrategy extends BaseStrategy { protected cronInstance?: CronExpression; constructor(scheduleConfig: EggScheduleConfig, agent: Agent, key: string) { super(scheduleConfig, agent, key); const { interval, cron, cronOptions, immediate } = this.scheduleConfig; assert(interval || cron || immediate, `[@eggjs/schedule] ${this.key} \`schedule.interval\` or \`schedule.cron\` or \`schedule.immediate\` must be present`); // init cron parser if (cron) { try { this.cronInstance = cronParser.parseExpression(cron, cronOptions); } catch (err: any) { throw new TypeError( `[@eggjs/schedule] ${this.key} parse cron instruction(${cron}) error: ${err.message}`, { cause: err }); } } } protected handler() { throw new TypeError(`[@eggjs/schedule] ${this.key} strategy should override \`handler()\` method`); } start() { /* istanbul ignore next */ if (this.agent.schedule.closed) return; if (this.scheduleConfig.immediate) { this.logger.info(`[Timer] ${this.key} next time will execute immediate`); setImmediate(() => this.handler()); } else { this.#scheduleNext(); } } #scheduleNext() { /* istanbul ignore next */ if (this.agent.schedule.closed) return; // get next tick const nextTick = this.getNextTick(); if (nextTick) { this.logger.info( `[Timer] ${this.key} next time will execute after ${nextTick}ms at ${logDate(new Date(Date.now() + nextTick))}`); this.safeTimeout(() => this.handler(), nextTick); } else { this.logger.info(`[Timer] ${this.key} reach endDate, will stop`); } } onJobStart() { // Next execution will trigger task at a fix rate, regardless of its execution time. this.#scheduleNext(); } /** * calculate next tick * * @return {Number|undefined} time interval, if out of range then return `undefined` */ protected getNextTick(): number | undefined { // interval-style if (this.scheduleConfig.interval) { return ms(this.scheduleConfig.interval); } // cron-style if (this.cronInstance) { // calculate next cron tick const now = Date.now(); let nextTick: number; // loop to find next feature time do { try { const nextInterval = this.cronInstance.next(); nextTick = nextInterval.getTime(); } catch (err) { // Error: Out of the timespan range this.logger.info(`[Timer] ${this.key} cron out of the timespan range, error: %s`, err); return; } } while (now >= nextTick); return nextTick - now; } // won\'t run here } protected safeTimeout(handler: () => void, delay: number, ...args: any[]) { const fn = delay < safeTimers.maxInterval ? setTimeout : safeTimers.setTimeout; return fn(handler, delay, ...args); } }