node-schedule 1.3.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/README.md +98 -51
- package/UPGRADING.md +20 -0
- package/index.js +17 -0
- package/lib/Invocation.js +348 -0
- package/lib/Job.js +287 -0
- package/lib/schedule.js +55 -598
- package/lib/utils/dateUtils.js +11 -0
- package/package.json +35 -13
- package/.eslintrc +0 -17
- package/.travis.yml +0 -15
- package/CONTRIBUTING.md +0 -31
- package/test/.eslintrc +0 -5
- package/test/cancel-long-running-jobs.js +0 -24
- package/test/convenience-method-test.js +0 -713
- package/test/date-convenience-methods-test.js +0 -59
- package/test/es6/job-test.js +0 -33
- package/test/job-test.js +0 -517
- package/test/range-test.js +0 -77
- package/test/recurrence-rule-test.js +0 -375
- package/test/schedule-cron-jobs.js +0 -134
- package/test/start-end-test.js +0 -327
package/lib/schedule.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
'use strict';
|
|
3
2
|
|
|
4
3
|
/*
|
|
@@ -6,276 +5,7 @@
|
|
|
6
5
|
A cron-like and not-cron-like job scheduler for Node.
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
util = require('util'),
|
|
11
|
-
cronParser = require('cron-parser'),
|
|
12
|
-
CronDate = require('cron-parser/lib/date'),
|
|
13
|
-
lt = require('long-timeout'),
|
|
14
|
-
sorted = require('sorted-array-functions');
|
|
15
|
-
|
|
16
|
-
/* Job object */
|
|
17
|
-
var anonJobCounter = 0;
|
|
18
|
-
var scheduledJobs = {};
|
|
19
|
-
|
|
20
|
-
function isValidDate(date) {
|
|
21
|
-
// Taken from http://stackoverflow.com/a/12372720/1562178
|
|
22
|
-
// If getTime() returns NaN it'll return false anyway
|
|
23
|
-
return date.getTime() === date.getTime();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function Job(name, job, callback) {
|
|
27
|
-
// setup a private pendingInvocations variable
|
|
28
|
-
var pendingInvocations = [];
|
|
29
|
-
|
|
30
|
-
//setup a private number of invocations variable
|
|
31
|
-
var triggeredJobs = 0;
|
|
32
|
-
|
|
33
|
-
// Set scope vars
|
|
34
|
-
var jobName = name && typeof name === 'string' ? name : '<Anonymous Job ' + (++anonJobCounter) + '>';
|
|
35
|
-
this.job = name && typeof name === 'function' ? name : job;
|
|
36
|
-
|
|
37
|
-
// Make sure callback is actually a callback
|
|
38
|
-
if (this.job === name) {
|
|
39
|
-
// Name wasn't provided and maybe a callback is there
|
|
40
|
-
this.callback = typeof job === 'function' ? job : false;
|
|
41
|
-
} else {
|
|
42
|
-
// Name was provided, and maybe a callback is there
|
|
43
|
-
this.callback = typeof callback === 'function' ? callback : false;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Check for generator
|
|
47
|
-
if (typeof this.job === 'function' &&
|
|
48
|
-
this.job.prototype &&
|
|
49
|
-
this.job.prototype.next) {
|
|
50
|
-
this.job = function() {
|
|
51
|
-
return this.next().value;
|
|
52
|
-
}.bind(this.job.call(this));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// define properties
|
|
56
|
-
Object.defineProperty(this, 'name', {
|
|
57
|
-
value: jobName,
|
|
58
|
-
writable: false,
|
|
59
|
-
enumerable: true
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// method that require private access
|
|
63
|
-
this.trackInvocation = function(invocation) {
|
|
64
|
-
// add to our invocation list
|
|
65
|
-
sorted.add(pendingInvocations, invocation, sorter);
|
|
66
|
-
return true;
|
|
67
|
-
};
|
|
68
|
-
this.stopTrackingInvocation = function(invocation) {
|
|
69
|
-
var invIdx = pendingInvocations.indexOf(invocation);
|
|
70
|
-
if (invIdx > -1) {
|
|
71
|
-
pendingInvocations.splice(invIdx, 1);
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return false;
|
|
76
|
-
};
|
|
77
|
-
this.triggeredJobs = function() {
|
|
78
|
-
return triggeredJobs;
|
|
79
|
-
};
|
|
80
|
-
this.setTriggeredJobs = function(triggeredJob) {
|
|
81
|
-
triggeredJobs = triggeredJob;
|
|
82
|
-
};
|
|
83
|
-
this.cancel = function(reschedule) {
|
|
84
|
-
reschedule = (typeof reschedule == 'boolean') ? reschedule : false;
|
|
85
|
-
|
|
86
|
-
var inv, newInv;
|
|
87
|
-
var newInvs = [];
|
|
88
|
-
for (var j = 0; j < pendingInvocations.length; j++) {
|
|
89
|
-
inv = pendingInvocations[j];
|
|
90
|
-
|
|
91
|
-
cancelInvocation(inv);
|
|
92
|
-
|
|
93
|
-
if (reschedule && inv.recurrenceRule.recurs) {
|
|
94
|
-
newInv = scheduleNextRecurrence(inv.recurrenceRule, this, inv.fireDate, inv.endDate);
|
|
95
|
-
if (newInv !== null) {
|
|
96
|
-
newInvs.push(newInv);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
pendingInvocations = [];
|
|
102
|
-
|
|
103
|
-
for (var k = 0; k < newInvs.length; k++) {
|
|
104
|
-
this.trackInvocation(newInvs[k]);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// remove from scheduledJobs if reschedule === false
|
|
108
|
-
if (!reschedule) {
|
|
109
|
-
if (this.name) {
|
|
110
|
-
delete scheduledJobs[this.name];
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return true;
|
|
115
|
-
};
|
|
116
|
-
this.cancelNext = function(reschedule) {
|
|
117
|
-
reschedule = (typeof reschedule == 'boolean') ? reschedule : true;
|
|
118
|
-
|
|
119
|
-
if (!pendingInvocations.length) {
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
var newInv;
|
|
124
|
-
var nextInv = pendingInvocations.shift();
|
|
125
|
-
|
|
126
|
-
cancelInvocation(nextInv);
|
|
127
|
-
|
|
128
|
-
if (reschedule && nextInv.recurrenceRule.recurs) {
|
|
129
|
-
newInv = scheduleNextRecurrence(nextInv.recurrenceRule, this, nextInv.fireDate, nextInv.endDate);
|
|
130
|
-
if (newInv !== null) {
|
|
131
|
-
this.trackInvocation(newInv);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return true;
|
|
136
|
-
};
|
|
137
|
-
this.reschedule = function(spec) {
|
|
138
|
-
var inv;
|
|
139
|
-
var cInvs = pendingInvocations.slice();
|
|
140
|
-
|
|
141
|
-
for (var j = 0; j < cInvs.length; j++) {
|
|
142
|
-
inv = cInvs[j];
|
|
143
|
-
|
|
144
|
-
cancelInvocation(inv);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
pendingInvocations = [];
|
|
148
|
-
|
|
149
|
-
if (this.schedule(spec)) {
|
|
150
|
-
this.setTriggeredJobs(0);
|
|
151
|
-
return true;
|
|
152
|
-
} else {
|
|
153
|
-
pendingInvocations = cInvs;
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
this.nextInvocation = function() {
|
|
158
|
-
if (!pendingInvocations.length) {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
return pendingInvocations[0].fireDate;
|
|
162
|
-
};
|
|
163
|
-
this.pendingInvocations = function() {
|
|
164
|
-
return pendingInvocations;
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
util.inherits(Job, events.EventEmitter);
|
|
169
|
-
|
|
170
|
-
Job.prototype.invoke = function(fireDate) {
|
|
171
|
-
if (typeof this.job == 'function') {
|
|
172
|
-
this.setTriggeredJobs(this.triggeredJobs() + 1);
|
|
173
|
-
this.job(fireDate);
|
|
174
|
-
} else {
|
|
175
|
-
this.job.execute(fireDate);
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
Job.prototype.runOnDate = function(date) {
|
|
180
|
-
return this.schedule(date);
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
Job.prototype.schedule = function(spec) {
|
|
184
|
-
var self = this;
|
|
185
|
-
var success = false;
|
|
186
|
-
var inv;
|
|
187
|
-
var start;
|
|
188
|
-
var end;
|
|
189
|
-
var tz;
|
|
190
|
-
|
|
191
|
-
// save passed-in value before 'spec' is replaced
|
|
192
|
-
if (typeof spec === 'object' && 'tz' in spec) {
|
|
193
|
-
tz = spec.tz;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (typeof spec === 'object' && spec.rule) {
|
|
197
|
-
start = spec.start || undefined;
|
|
198
|
-
end = spec.end || undefined;
|
|
199
|
-
spec = spec.rule;
|
|
200
|
-
|
|
201
|
-
if (start) {
|
|
202
|
-
if (!(start instanceof Date)) {
|
|
203
|
-
start = new Date(start);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
start = new CronDate(start, tz);
|
|
207
|
-
if (!isValidDate(start) || start.getTime() < Date.now()) {
|
|
208
|
-
start = undefined;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (end && !(end instanceof Date) && !isValidDate(end = new Date(end))) {
|
|
213
|
-
end = undefined;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (end) {
|
|
217
|
-
end = new CronDate(end, tz);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
var res = cronParser.parseExpression(spec, { currentDate: start, tz: tz });
|
|
223
|
-
inv = scheduleNextRecurrence(res, self, start, end);
|
|
224
|
-
if (inv !== null) {
|
|
225
|
-
success = self.trackInvocation(inv);
|
|
226
|
-
}
|
|
227
|
-
} catch (err) {
|
|
228
|
-
var type = typeof spec;
|
|
229
|
-
if ((type === 'string') || (type === 'number')) {
|
|
230
|
-
spec = new Date(spec);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if ((spec instanceof Date) && (isValidDate(spec))) {
|
|
234
|
-
spec = new CronDate(spec);
|
|
235
|
-
if (spec.getTime() >= Date.now()) {
|
|
236
|
-
inv = new Invocation(self, spec);
|
|
237
|
-
scheduleInvocation(inv);
|
|
238
|
-
success = self.trackInvocation(inv);
|
|
239
|
-
}
|
|
240
|
-
} else if (type === 'object') {
|
|
241
|
-
if (!(spec instanceof RecurrenceRule)) {
|
|
242
|
-
var r = new RecurrenceRule();
|
|
243
|
-
if ('year' in spec) {
|
|
244
|
-
r.year = spec.year;
|
|
245
|
-
}
|
|
246
|
-
if ('month' in spec) {
|
|
247
|
-
r.month = spec.month;
|
|
248
|
-
}
|
|
249
|
-
if ('date' in spec) {
|
|
250
|
-
r.date = spec.date;
|
|
251
|
-
}
|
|
252
|
-
if ('dayOfWeek' in spec) {
|
|
253
|
-
r.dayOfWeek = spec.dayOfWeek;
|
|
254
|
-
}
|
|
255
|
-
if ('hour' in spec) {
|
|
256
|
-
r.hour = spec.hour;
|
|
257
|
-
}
|
|
258
|
-
if ('minute' in spec) {
|
|
259
|
-
r.minute = spec.minute;
|
|
260
|
-
}
|
|
261
|
-
if ('second' in spec) {
|
|
262
|
-
r.second = spec.second;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
spec = r;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
spec.tz = tz;
|
|
269
|
-
inv = scheduleNextRecurrence(spec, self, start, end);
|
|
270
|
-
if (inv !== null) {
|
|
271
|
-
success = self.trackInvocation(inv);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
scheduledJobs[this.name] = this;
|
|
277
|
-
return success;
|
|
278
|
-
};
|
|
8
|
+
const { Job, scheduledJobs } = require('./Job')
|
|
279
9
|
|
|
280
10
|
/* API
|
|
281
11
|
invoke()
|
|
@@ -289,327 +19,22 @@ Job.prototype.schedule = function(spec) {
|
|
|
289
19
|
job: readwrite
|
|
290
20
|
*/
|
|
291
21
|
|
|
292
|
-
/* DoesntRecur rule */
|
|
293
|
-
var DoesntRecur = new RecurrenceRule();
|
|
294
|
-
DoesntRecur.recurs = false;
|
|
295
|
-
|
|
296
|
-
/* Invocation object */
|
|
297
|
-
function Invocation(job, fireDate, recurrenceRule, endDate) {
|
|
298
|
-
this.job = job;
|
|
299
|
-
this.fireDate = fireDate;
|
|
300
|
-
this.endDate = endDate;
|
|
301
|
-
this.recurrenceRule = recurrenceRule || DoesntRecur;
|
|
302
|
-
|
|
303
|
-
this.timerID = null;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function sorter(a, b) {
|
|
307
|
-
return (a.fireDate.getTime() - b.fireDate.getTime());
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/* Range object */
|
|
311
|
-
function Range(start, end, step) {
|
|
312
|
-
this.start = start || 0;
|
|
313
|
-
this.end = end || 60;
|
|
314
|
-
this.step = step || 1;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
Range.prototype.contains = function(val) {
|
|
318
|
-
if (this.step === null || this.step === 1) {
|
|
319
|
-
return (val >= this.start && val <= this.end);
|
|
320
|
-
} else {
|
|
321
|
-
for (var i = this.start; i < this.end; i += this.step) {
|
|
322
|
-
if (i === val) {
|
|
323
|
-
return true;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return false;
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
/* RecurrenceRule object */
|
|
332
|
-
/*
|
|
333
|
-
Interpreting each property:
|
|
334
|
-
null - any value is valid
|
|
335
|
-
number - fixed value
|
|
336
|
-
Range - value must fall in range
|
|
337
|
-
array - value must validate against any item in list
|
|
338
|
-
|
|
339
|
-
NOTE: Cron months are 1-based, but RecurrenceRule months are 0-based.
|
|
340
|
-
*/
|
|
341
|
-
function RecurrenceRule(year, month, date, dayOfWeek, hour, minute, second) {
|
|
342
|
-
this.recurs = true;
|
|
343
|
-
|
|
344
|
-
this.year = (year == null) ? null : year;
|
|
345
|
-
this.month = (month == null) ? null : month;
|
|
346
|
-
this.date = (date == null) ? null : date;
|
|
347
|
-
this.dayOfWeek = (dayOfWeek == null) ? null : dayOfWeek;
|
|
348
|
-
this.hour = (hour == null) ? null : hour;
|
|
349
|
-
this.minute = (minute == null) ? null : minute;
|
|
350
|
-
this.second = (second == null) ? 0 : second;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
RecurrenceRule.prototype.isValid = function() {
|
|
354
|
-
function isValidType(num) {
|
|
355
|
-
if (Array.isArray(num) || (num instanceof Array)) {
|
|
356
|
-
return num.every(function(e) {
|
|
357
|
-
return isValidType(e);
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
return !(Number.isNaN(Number(num)) && !(num instanceof Range));
|
|
361
|
-
}
|
|
362
|
-
if (this.month !== null && (this.month < 0 || this.month > 11 || !isValidType(this.month))) {
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
if (this.dayOfWeek !== null && (this.dayOfWeek < 0 || this.dayOfWeek > 6 || !isValidType(this.dayOfWeek))) {
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
368
|
-
if (this.hour !== null && (this.hour < 0 || this.hour > 23 || !isValidType(this.hour))) {
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
if (this.minute !== null && (this.minute < 0 || this.minute > 59 || !isValidType(this.minute))) {
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
if (this.second !== null && (this.second < 0 || this.second > 59 || !isValidType(this.second))) {
|
|
375
|
-
return false;
|
|
376
|
-
}
|
|
377
|
-
if (this.date !== null) {
|
|
378
|
-
if(!isValidType(this.date)) {
|
|
379
|
-
return false;
|
|
380
|
-
}
|
|
381
|
-
switch (this.month) {
|
|
382
|
-
case 3:
|
|
383
|
-
case 5:
|
|
384
|
-
case 8:
|
|
385
|
-
case 10:
|
|
386
|
-
if (this.date < 1 || this. date > 30) {
|
|
387
|
-
return false;
|
|
388
|
-
}
|
|
389
|
-
break;
|
|
390
|
-
case 1:
|
|
391
|
-
if (this.date < 1 || this. date > 29) {
|
|
392
|
-
return false;
|
|
393
|
-
}
|
|
394
|
-
break;
|
|
395
|
-
default:
|
|
396
|
-
if (this.date < 1 || this. date > 31) {
|
|
397
|
-
return false;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return true;
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
RecurrenceRule.prototype.nextInvocationDate = function(base) {
|
|
405
|
-
var next = this._nextInvocationDate(base);
|
|
406
|
-
return next ? next.toDate() : null;
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
RecurrenceRule.prototype._nextInvocationDate = function(base) {
|
|
410
|
-
base = ((base instanceof CronDate) || (base instanceof Date)) ? base : (new Date());
|
|
411
|
-
if (!this.recurs) {
|
|
412
|
-
return null;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if(!this.isValid()) {
|
|
416
|
-
return null;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
var now = new CronDate(Date.now(), this.tz);
|
|
420
|
-
var fullYear = now.getFullYear();
|
|
421
|
-
if ((this.year !== null) &&
|
|
422
|
-
(typeof this.year == 'number') &&
|
|
423
|
-
(this.year < fullYear)) {
|
|
424
|
-
return null;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
var next = new CronDate(base.getTime(), this.tz);
|
|
428
|
-
next.addSecond();
|
|
429
|
-
|
|
430
|
-
while (true) {
|
|
431
|
-
if (this.year !== null) {
|
|
432
|
-
fullYear = next.getFullYear();
|
|
433
|
-
if ((typeof this.year == 'number') && (this.year < fullYear)) {
|
|
434
|
-
next = null;
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (!recurMatch(fullYear, this.year)) {
|
|
439
|
-
next.addYear();
|
|
440
|
-
next.setMonth(0);
|
|
441
|
-
next.setDate(1);
|
|
442
|
-
next.setHours(0);
|
|
443
|
-
next.setMinutes(0);
|
|
444
|
-
next.setSeconds(0);
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
if (this.month != null && !recurMatch(next.getMonth(), this.month)) {
|
|
449
|
-
next.addMonth();
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
452
|
-
if (this.date != null && !recurMatch(next.getDate(), this.date)) {
|
|
453
|
-
next.addDay();
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
-
if (this.dayOfWeek != null && !recurMatch(next.getDay(), this.dayOfWeek)) {
|
|
457
|
-
next.addDay();
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
if (this.hour != null && !recurMatch(next.getHours(), this.hour)) {
|
|
461
|
-
next.addHour();
|
|
462
|
-
continue;
|
|
463
|
-
}
|
|
464
|
-
if (this.minute != null && !recurMatch(next.getMinutes(), this.minute)) {
|
|
465
|
-
next.addMinute();
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
if (this.second != null && !recurMatch(next.getSeconds(), this.second)) {
|
|
469
|
-
next.addSecond();
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
break;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
return next;
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
function recurMatch(val, matcher) {
|
|
480
|
-
if (matcher == null) {
|
|
481
|
-
return true;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (typeof matcher === 'number') {
|
|
485
|
-
return (val === matcher);
|
|
486
|
-
} else if(typeof matcher === 'string') {
|
|
487
|
-
return (val === Number(matcher));
|
|
488
|
-
} else if (matcher instanceof Range) {
|
|
489
|
-
return matcher.contains(val);
|
|
490
|
-
} else if (Array.isArray(matcher) || (matcher instanceof Array)) {
|
|
491
|
-
for (var i = 0; i < matcher.length; i++) {
|
|
492
|
-
if (recurMatch(val, matcher[i])) {
|
|
493
|
-
return true;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
return false;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/* Date-based scheduler */
|
|
502
|
-
function runOnDate(date, job) {
|
|
503
|
-
var now = Date.now();
|
|
504
|
-
var then = date.getTime();
|
|
505
|
-
|
|
506
|
-
return lt.setTimeout(function() {
|
|
507
|
-
if (then > Date.now())
|
|
508
|
-
runOnDate(date, job);
|
|
509
|
-
else
|
|
510
|
-
job();
|
|
511
|
-
}, (then < now ? 0 : then - now));
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
var invocations = [];
|
|
515
|
-
var currentInvocation = null;
|
|
516
|
-
|
|
517
|
-
function scheduleInvocation(invocation) {
|
|
518
|
-
sorted.add(invocations, invocation, sorter);
|
|
519
|
-
prepareNextInvocation();
|
|
520
|
-
var date = invocation.fireDate instanceof CronDate ? invocation.fireDate.toDate() : invocation.fireDate;
|
|
521
|
-
invocation.job.emit('scheduled', date);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
function prepareNextInvocation() {
|
|
525
|
-
if (invocations.length > 0 && currentInvocation !== invocations[0]) {
|
|
526
|
-
if (currentInvocation !== null) {
|
|
527
|
-
lt.clearTimeout(currentInvocation.timerID);
|
|
528
|
-
currentInvocation.timerID = null;
|
|
529
|
-
currentInvocation = null;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
currentInvocation = invocations[0];
|
|
533
|
-
|
|
534
|
-
var job = currentInvocation.job;
|
|
535
|
-
var cinv = currentInvocation;
|
|
536
|
-
currentInvocation.timerID = runOnDate(currentInvocation.fireDate, function() {
|
|
537
|
-
currentInvocationFinished();
|
|
538
|
-
|
|
539
|
-
if (job.callback) {
|
|
540
|
-
job.callback();
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
if (cinv.recurrenceRule.recurs || cinv.recurrenceRule._endDate === null) {
|
|
544
|
-
var inv = scheduleNextRecurrence(cinv.recurrenceRule, cinv.job, cinv.fireDate, cinv.endDate);
|
|
545
|
-
if (inv !== null) {
|
|
546
|
-
inv.job.trackInvocation(inv);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
job.stopTrackingInvocation(cinv);
|
|
551
|
-
|
|
552
|
-
job.invoke(cinv.fireDate instanceof CronDate ? cinv.fireDate.toDate() : cinv.fireDate);
|
|
553
|
-
job.emit('run');
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function currentInvocationFinished() {
|
|
559
|
-
invocations.shift();
|
|
560
|
-
currentInvocation = null;
|
|
561
|
-
prepareNextInvocation();
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
function cancelInvocation(invocation) {
|
|
565
|
-
var idx = invocations.indexOf(invocation);
|
|
566
|
-
if (idx > -1) {
|
|
567
|
-
invocations.splice(idx, 1);
|
|
568
|
-
if (invocation.timerID !== null) {
|
|
569
|
-
lt.clearTimeout(invocation.timerID);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if (currentInvocation === invocation) {
|
|
573
|
-
currentInvocation = null;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
invocation.job.emit('canceled', invocation.fireDate);
|
|
577
|
-
prepareNextInvocation();
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
/* Recurrence scheduler */
|
|
582
|
-
function scheduleNextRecurrence(rule, job, prevDate, endDate) {
|
|
583
|
-
|
|
584
|
-
prevDate = (prevDate instanceof CronDate) ? prevDate : new CronDate();
|
|
585
|
-
|
|
586
|
-
var date = (rule instanceof RecurrenceRule) ? rule._nextInvocationDate(prevDate) : rule.next();
|
|
587
|
-
if (date === null) {
|
|
588
|
-
return null;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if ((endDate instanceof CronDate) && date.getTime() > endDate.getTime()) {
|
|
592
|
-
return null;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
var inv = new Invocation(job, date, rule, endDate);
|
|
596
|
-
scheduleInvocation(inv);
|
|
597
|
-
|
|
598
|
-
return inv;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
22
|
/* Convenience methods */
|
|
602
23
|
function scheduleJob() {
|
|
603
24
|
if (arguments.length < 2) {
|
|
604
|
-
|
|
25
|
+
throw new RangeError('Invalid number of arguments');
|
|
605
26
|
}
|
|
606
27
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
28
|
+
const name = (arguments.length >= 3 && typeof arguments[0] === 'string') ? arguments[0] : null;
|
|
29
|
+
const spec = name ? arguments[1] : arguments[0];
|
|
30
|
+
const method = name ? arguments[2] : arguments[1];
|
|
31
|
+
const callback = name ? arguments[3] : arguments[2];
|
|
32
|
+
|
|
33
|
+
if (typeof method !== 'function') {
|
|
34
|
+
throw new RangeError('The job method must be a function.');
|
|
35
|
+
}
|
|
611
36
|
|
|
612
|
-
|
|
37
|
+
const job = new Job(name, method, callback);
|
|
613
38
|
|
|
614
39
|
if (job.schedule(spec)) {
|
|
615
40
|
return job;
|
|
@@ -623,22 +48,24 @@ function rescheduleJob(job, spec) {
|
|
|
623
48
|
if (job.reschedule(spec)) {
|
|
624
49
|
return job;
|
|
625
50
|
}
|
|
626
|
-
} else if (typeof job
|
|
627
|
-
if (
|
|
51
|
+
} else if (typeof job === 'string') {
|
|
52
|
+
if (Object.prototype.hasOwnProperty.call(scheduledJobs, job)) {
|
|
628
53
|
if (scheduledJobs[job].reschedule(spec)) {
|
|
629
54
|
return scheduledJobs[job];
|
|
630
55
|
}
|
|
56
|
+
} else {
|
|
57
|
+
throw new Error('Cannot reschedule one-off job by name, pass job reference instead')
|
|
631
58
|
}
|
|
632
59
|
}
|
|
633
60
|
return null;
|
|
634
61
|
}
|
|
635
62
|
|
|
636
63
|
function cancelJob(job) {
|
|
637
|
-
|
|
64
|
+
let success = false;
|
|
638
65
|
if (job instanceof Job) {
|
|
639
66
|
success = job.cancel();
|
|
640
67
|
} else if (typeof job == 'string' || job instanceof String) {
|
|
641
|
-
if (job in scheduledJobs &&
|
|
68
|
+
if (job in scheduledJobs && Object.prototype.hasOwnProperty.call(scheduledJobs, job)) {
|
|
642
69
|
success = scheduledJobs[job].cancel();
|
|
643
70
|
}
|
|
644
71
|
}
|
|
@@ -646,12 +73,42 @@ function cancelJob(job) {
|
|
|
646
73
|
return success;
|
|
647
74
|
}
|
|
648
75
|
|
|
76
|
+
function gracefulShutdown() {
|
|
77
|
+
const jobs = Object.keys(scheduledJobs).map(key => scheduledJobs[key]);
|
|
78
|
+
jobs.forEach(function (job) {
|
|
79
|
+
job.cancel();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let running = false;
|
|
83
|
+
for (let i = 0; i < jobs.length; i++) {
|
|
84
|
+
if (jobs[i].running > 0) {
|
|
85
|
+
running = true;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return new Promise(function (resolve) {
|
|
91
|
+
if (running) {
|
|
92
|
+
setInterval(function () {
|
|
93
|
+
for (let i = 0; i < jobs.length; i++) {
|
|
94
|
+
if (jobs[i].running > 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
resolve();
|
|
100
|
+
}, 500);
|
|
101
|
+
} else {
|
|
102
|
+
resolve();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
649
107
|
/* Public API */
|
|
650
|
-
module.exports
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
module.exports.cancelJob = cancelJob;
|
|
108
|
+
module.exports = {
|
|
109
|
+
scheduleJob,
|
|
110
|
+
rescheduleJob,
|
|
111
|
+
scheduledJobs,
|
|
112
|
+
cancelJob,
|
|
113
|
+
gracefulShutdown,
|
|
114
|
+
}
|