timer.js 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. 'use strict';
  2. const Strategy = require('./base');
  3. const parser = require('cron-parser');
  4. const ms = require('humanize-ms');
  5. const safetimers = require('safe-timers');
  6. const assert = require('assert');
  7. const utility = require('utility');
  8. const is = require('is-type-of');
  9. const CRON_INSTANCE = Symbol('cron_instance');
  10. module.exports = class TimerStrategy extends Strategy {
  11. constructor(...args) {
  12. super(...args);
  13. const { interval, cron, cronOptions, immediate } = this.schedule;
  14. assert(interval || cron || immediate, `[egg-schedule] ${this.key} schedule.interval or schedule.cron or schedule.immediate must be present`);
  15. assert(is.function(this.handler), `[egg-schedule] ${this.key} strategy should override \`handler()\` method`);
  16. // init cron parser
  17. if (cron) {
  18. try {
  19. this[CRON_INSTANCE] = parser.parseExpression(cron, cronOptions);
  20. } catch (err) {
  21. err.message = `[egg-schedule] ${this.key} parse cron instruction(${cron}) error: ${err.message}`;
  22. throw err;
  23. }
  24. }
  25. }
  26. start() {
  27. /* istanbul ignore next */
  28. if (this.agent.schedule.closed) return;
  29. if (this.schedule.immediate) {
  30. this.logger.info(`[Timer] ${this.key} next time will execute immediate`);
  31. setImmediate(() => this.handler());
  32. } else {
  33. this._scheduleNext();
  34. }
  35. }
  36. _scheduleNext() {
  37. /* istanbul ignore next */
  38. if (this.agent.schedule.closed) return;
  39. // get next tick
  40. const nextTick = this.getNextTick();
  41. if (nextTick) {
  42. this.logger.info(`[Timer] ${this.key} next time will execute after ${nextTick}ms at ${utility.logDate(new Date(Date.now() + nextTick))}`);
  43. this.safeTimeout(() => this.handler(), nextTick);
  44. } else {
  45. this.logger.info(`[Timer] ${this.key} reach endDate, will stop`);
  46. }
  47. }
  48. onJobStart() {
  49. // Next execution will trigger task at a fix rate, regardless of its execution time.
  50. this._scheduleNext();
  51. }
  52. /**
  53. * calculate next tick
  54. *
  55. * @return {Number} time interval, if out of range then return `undefined`
  56. */
  57. getNextTick() {
  58. // interval-style
  59. if (this.schedule.interval) return ms(this.schedule.interval);
  60. // cron-style
  61. if (this[CRON_INSTANCE]) {
  62. // calculate next cron tick
  63. const now = Date.now();
  64. let nextTick;
  65. let nextInterval;
  66. // loop to find next feature time
  67. do {
  68. try {
  69. nextInterval = this[CRON_INSTANCE].next();
  70. nextTick = nextInterval.getTime();
  71. } catch (err) {
  72. // Error: Out of the timespan range
  73. return;
  74. }
  75. } while (now >= nextTick);
  76. return nextTick - now;
  77. }
  78. }
  79. safeTimeout(handler, delay, ...args) {
  80. const fn = delay < safetimers.maxInterval ? setTimeout : safetimers.setTimeout;
  81. return fn(handler, delay, ...args);
  82. }
  83. };