'use strict'; const Strategy = require('./base'); const parser = require('cron-parser'); const ms = require('humanize-ms'); const safetimers = require('safe-timers'); const assert = require('assert'); const utility = require('utility'); const is = require('is-type-of'); const CRON_INSTANCE = Symbol('cron_instance'); module.exports = class TimerStrategy extends Strategy { constructor(...args) { super(...args); const { interval, cron, cronOptions, immediate } = this.schedule; assert(interval || cron || immediate, `[egg-schedule] ${this.key} schedule.interval or schedule.cron or schedule.immediate must be present`); assert(is.function(this.handler), `[egg-schedule] ${this.key} strategy should override \`handler()\` method`); // init cron parser if (cron) { try { this[CRON_INSTANCE] = parser.parseExpression(cron, cronOptions); } catch (err) { err.message = `[egg-schedule] ${this.key} parse cron instruction(${cron}) error: ${err.message}`; throw err; } } } start() { /* istanbul ignore next */ if (this.agent.schedule.closed) return; if (this.schedule.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 ${utility.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} time interval, if out of range then return `undefined` */ getNextTick() { // interval-style if (this.schedule.interval) return ms(this.schedule.interval); // cron-style if (this[CRON_INSTANCE]) { // calculate next cron tick const now = Date.now(); let nextTick; let nextInterval; // loop to find next feature time do { try { nextInterval = this[CRON_INSTANCE].next(); nextTick = nextInterval.getTime(); } catch (err) { // Error: Out of the timespan range return; } } while (now >= nextTick); return nextTick - now; } } safeTimeout(handler, delay, ...args) { const fn = delay < safetimers.maxInterval ? setTimeout : safetimers.setTimeout; return fn(handler, delay, ...args); } };