From 3cd8554cafded61bea1e39eb2587f55d907e3de5 Mon Sep 17 00:00:00 2001 From: grullo90 Date: Fri, 22 Dec 2017 16:09:03 +0100 Subject: [PATCH] Release 1.1.0 --- lib/{task.js => classes/Task.js} | 137 +++++++++++++++++------ lib/index.js | 180 +++++++++++++++++++------------ package.json | 2 +- 3 files changed, 216 insertions(+), 103 deletions(-) rename lib/{task.js => classes/Task.js} (64%) diff --git a/lib/task.js b/lib/classes/Task.js similarity index 64% rename from lib/task.js rename to lib/classes/Task.js index 7a28a88..db6e297 100644 --- a/lib/task.js +++ b/lib/classes/Task.js @@ -16,7 +16,6 @@ const XP = require('expandjs'), * A class used by XPSchedule to provide scheduling functionality. * * @class Task - * @since 1.0.0 * @description A class used by XPSchedule to provide scheduling functionality * @keywords nodejs, expandjs * @source https://github.com/expandjs/xp-scheduler/blob/master/lib/task.js @@ -25,11 +24,13 @@ module.exports = new XP.Class('Task', { /** * @constructs - * @param {Object} [options] The task's options + * @param {Object} options The task's options + * @param {Function} options.handler The function to execute * @param {Date} [options.endDate] The recurrence's date limit * @param {string} [options.frequency = "precise"] The recurrence's repetition frequency + * @param {string} [options.id] The recurrence's identifier. * @param {number} [options.interval] The interval between each recurrence's iteration - * @param {number} [options.iterations = 1] The number of recurrence's iterations + * @param {number} [options.iterations] The number of recurrence's iterations * @param {number} [options.month] The recurrence's month * @param {number} [options.monthDay] The recurrence's day of the month * @param {Date} [options.startDate] The recurrence's start date @@ -37,38 +38,74 @@ module.exports = new XP.Class('Task', { * @param {string} [options.week] The recurrence's week * @param {string} [options.weekDay] The recurrence's day of the week * @param {Array} [options.weekDays] The recurrence's days of the week + * @param {Function} [resolver] */ - initialize(options) { + initialize: { + promise: true, + value(options, resolver) { + + // Setting + this.options = options; + this.handler = this.options.handler; + this.endDate = this.options.endDate || null; + this.frequency = this.options.frequency || 'precise'; + this.id = this.options.id || XP.uuid(); + this.interval = this.options.interval || null; + this.iterations = this.frequency === 'precise' ? 1 : this.options.iterations || null; + this.month = this.options.month || null; + this.monthDay = this.options.monthDay || null; + this.startDate = this.options.startDate || new Date(); + this.startTime = this.options.startTime || null; + this.week = this.options.week || null; + this.weekDay = this.options.weekDay || null; + this.weekDays = this.options.weekDays || []; + + // Adapting + this.adaptee = new RRule({ + freq: RRule[this.frequencies[this.frequency]], + interval: this.interval, + count: this.iterations, + byweekday: this.weekDay ? RRule[this.days[this.weekDay]] : this.weekDays.map(day => RRule[this.days[day]]), + bymonth: this.month, + bymonthday: this.monthDay, + byhour: this.startTime && XP.toDefined(XP.toFinite(this.startTime.match(XP.timeRegex)[1])), + byminute: this.startTime && XP.toDefined(XP.toFinite(this.startTime.match(XP.timeRegex)[2])), + bysecond: this.startTime && XP.toDefined(XP.toFinite(this.startTime.match(XP.timeRegex)[4])), + bysetpos: this.week && this.weeks[this.week], + dtstart: this.startDate, + until: this.endDate + }); + + // Resolving + resolver(null, this); + } + }, + + /*********************************************************************/ + + /** + * Clears the timer id. + * + * @method clearTimeout + */ + clearTimeout() { + + // Clearing + if (this.timerId) { clearTimeout(this.timerId); } + }, + + /** + * Set the timer id. + * + * @method setTimeout + */ + setTimeout() { + + // Clearing + this.clearTimeout(); // Setting - this.options = options; - this.endDate = this.options.endDate || null; - this.frequency = this.options.frequency || 'precise'; - this.interval = this.options.interval || null; - this.iterations = this.frequency !== 'precise' ? this.options.iterations || null : 1; - this.month = this.options.month || null; - this.monthDay = this.options.monthDay || null; - this.startDate = this.options.startDate || new Date(); - this.startTime = this.options.startTime || null; - this.week = this.options.week || null; - this.weekDay = this.options.weekDay || null; - this.weekDays = this.options.weekDays || []; - - // Adapting - this.adaptee = new RRule({ - freq: RRule[this.frequencies[this.frequency]], - interval: this.interval, - count: this.iterations, - byweekday: this.weekDay ? RRule[this.days[this.weekDay]] : this.weekDays.map(day => RRule[this.days[day]]), - bymonth: this.month, - bymonthday: this.monthDay, - byhour: this.startTime && XP.toDefined(XP.toFinite(this.startTime.match(XP.timeRegex)[1])), - byminute: this.startTime && XP.toDefined(XP.toFinite(this.startTime.match(XP.timeRegex)[2])), - bysecond: this.startTime && XP.toDefined(XP.toFinite(this.startTime.match(XP.timeRegex)[4])), - bysetpos: this.week && this.weeks[this.week], - dtstart: this.startDate, - until: this.endDate - }); + this.timerId = this.setTimeout(this.handler, this.nextDate - Date.now()); }, /*********************************************************************/ @@ -133,6 +170,30 @@ module.exports = new XP.Class('Task', { validate(val) { return !this.frequencies[val] && 'string'; } }, + /** + * The function to execute. + * + * @property handler + * @type Function + */ + handler: { + set(val) { return this.handler || val; }, + validate(val) { return !XP.isFunction(val) && 'Function'; } + }, + + /** + * The recurrence's identifier. + * + * By default, an UUID will be assigned. + * + * @property id + * @type string + */ + id: { + set(val) { return this.id || val; }, + validate(val) { return !XP.isString(val, true) && 'string'; } + }, + /** * The interval between each recurrence's iteration. * @@ -210,6 +271,18 @@ module.exports = new XP.Class('Task', { validate(val) { return !XP.isNull(val) && !XP.isTime(val) && 'string'; } }, + /** + * The timer id of the next recurrence's iteration. + * + * @property timerId + * @type number + * @readonly + */ + timerId: { + set(val) { return val; }, + validate(val) { return !XP.isInt(val, true) && 'number'; } + }, + /** * The recurrence's week. * diff --git a/lib/index.js b/lib/index.js index 32be3f7..bfcfe71 100644 --- a/lib/index.js +++ b/lib/index.js @@ -9,7 +9,7 @@ // Const const XP = require('expandjs'), XPEmitter = require('xp-emitter'), - Task = require('./task'); + Task = require('./classes/Task'); /*********************************************************************/ @@ -18,12 +18,11 @@ const XP = require('expandjs'), * * @class XPScheduler * @extends XPEmitter /bower_components/xp-emitter/lib/index.js - * @since 1.0.0 * @description A server side class used to provide scheduling functionality * @keywords nodejs, expandjs * @source https://github.com/expandjs/xp-scheduler/blob/master/lib/index.js */ -module.exports = global.XPScheduler = new XP.Class('XPScheduler', { +module.exports = new XP.Class('XPScheduler', { // EXTENDS extends: XPEmitter, @@ -41,73 +40,53 @@ module.exports = global.XPScheduler = new XP.Class('XPScheduler', { XPEmitter.call(this); // Setting - this.scheduled = {}; - this.options = options; - this.interval = this.options.interval || 60000; + this.tasks = {}; + this.timers = {}; + this.options = options; + this.latest = Date.now(); + this.interval = this.options.interval || 60000; // Polling - setInterval(() => this.emit('poll'), this.interval); + setInterval(this._handlePoll.bind(this), this.interval); }, /*********************************************************************/ /** - * Adds the provided `handler` to the poll event and invokes it. - * - * @method poll - * @param {Function} handler - */ - poll(handler) { - - // Asserting - XP.assertArgument(XP.isFunction(handler), 1, 'Function'); - - // Listening - this.on('poll', handler); - - // Handling - handler(); - }, - - /** - * Adds a scheduled task. + * Removes a scheduled task. * - * @method schedule - * @param {Date} date - * @param {string} [id] - * @param {Function} [handler] - * @returns {string} + * @method remove + * @param {string} id + * @param {Function} [callback] */ - schedule: { + remove: { callback: true, - value(date, id, handler) { + value(id, callback) { // Asserting - XP.assertArgument(XP.isDate(date), 1, 'Date'); - XP.assertArgument(XP.isVoid(id) || XP.isString(id, true), 2, 'string'); - XP.assertArgument(XP.isFunction(handler), 3, 'Function'); + if (!XP.isString(id, true)) { callback(new XP.ValidationError('id', 'string')); return; } - // Preparing - if (!id) { id = XP.uuid(); } + // Clearing + if (this.timers[id]) { clearTimeout(this.timers[id]); } - // Unscheduling - if (this.scheduled[id]) { this.unschedule(id); } + // Deleting + delete this.tasks[id]; + delete this.timers[id]; - // Setting - this.scheduled[id] = setTimeout(() => { delete this.scheduled[id]; handler(); }, date - Date.now()); - - // Returning - return id; - } + // Callback + callback(null, null); + }, }, /** - * Returns a new task. + * Adds a scheduled task. * - * @method task + * @method schedule * @param {Object} options + * @param {Function} options.handler * @param {Date} [options.endDate] * @param {string} [options.frequency = "precise"] + * @param {string} [options.id] * @param {number} [options.interval] * @param {number} [options.iterations] * @param {number} [options.month] @@ -117,31 +96,34 @@ module.exports = global.XPScheduler = new XP.Class('XPScheduler', { * @param {string} [options.week] * @param {string} [options.weekDay] * @param {Array} [options.weekDays] - * @returns {Object} + * @param {Function} [callback] */ - task(options) { + schedule: { + callback: true, + value(options, callback) { - // Defining - return new Task(options); - }, + // Let + let task; - /** - * Removes a scheduled task. - * - * @method unschedule - * @param {string} id - * @returns {boolean} - */ - unschedule(id) { + // Waterfall + XP.waterfall([ + next => new Task(options, (err, res) => next(err, task = res)), // preparing task + next => this.remove(task.id, next) // removing task + ], error => { - // Asserting - XP.assertArgument(XP.isString(id, true), 1, 'string'); + // Checking + if (error) { callback(error); return; } - // Clearing - if (this.scheduled[id]) { clearTimeout(this.scheduled[id]); } + // Scheduling + this.tasks[task.id] = task; - // Deleting - return delete this.scheduled[id]; + // Handling + this._handleTask(task); + + // Callback + callback(null, task); + }); + } }, /*********************************************************************/ @@ -157,15 +139,73 @@ module.exports = global.XPScheduler = new XP.Class('XPScheduler', { validate(val) { return !XP.isInt(val, true) && 'number'; } }, + /** + * The timestamp of the latest poll. + * + * @property latest + * @type number + */ + latest: { + set(val) { return val; }, + validate(val) { return !XP.isInt(val, true) && 'number'; } + }, + /** * The scheduled tasks. * - * @property scheduled + * @property tasks * @type Object * @readonly */ - scheduled: { - set(val) { return this.scheduled || val; }, + tasks: { + set(val) { return this.tasks || val; }, validate(val) { return !XP.isObject(val) && 'Object'; } + }, + + /** + * The scheduled tasks timers. + * + * @property timers + * @type Object + * @readonly + */ + timers: { + set(val) { return this.timers || val; }, + validate(val) { return !XP.isObject(val) && 'Object'; } + }, + + /*********************************************************************/ + + // HANDLER + _handlePoll() { + + // Updating + this.latest = Date.now(); + + // Handling + Object.keys(this.tasks).forEach(id => this._handleTask(this.tasks[id])); + }, + + // HANDLER + _handleTask(task) { + + // Let + let date = task.nextDate; + + // Preventing + if (!date || date > this.latest + this.interval) { return; } + + // Setting + this.timers[task.id] = setTimeout(this._handleTimeout.bind(this, task), date - Date.now()); + }, + + // HANDLER + _handleTimeout(task) { + + // Handling + this._handleTask(task); + + // Executing + task.handler(); } }); diff --git a/package.json b/package.json index ba3140d..da5fbbc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xp-scheduler", "description": "A server side class used to provide scheduling functionality", - "version": "1.0.2", + "version": "1.1.0", "license": "BSD-3-Clause", "homepage": "https://expandjs.com/classes/xp-scheduler", "author": "ExpandJS (https://expandjs.com)",