@abtnode/queue 1.8.20 → 1.8.22

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/lib/index.js CHANGED
@@ -3,12 +3,14 @@ const uuid = require('uuid');
3
3
  const Queue = require('fastq');
4
4
  const EventEmitter = require('events');
5
5
  const tryWithTimeout = require('@abtnode/util/lib/try-with-timeout');
6
+ const sleep = require('@abtnode/util/lib/sleep');
6
7
  const debug = require('debug')('core/queue');
7
8
 
8
9
  const JobStore = require('./store');
9
10
  const logger = require('./logger');
10
11
 
11
12
  const CANCELLED = '__CANCELLED__';
13
+ const MIN_DELAY = 5;
12
14
 
13
15
  /**
14
16
  *
@@ -21,6 +23,7 @@ const CANCELLED = '__CANCELLED__';
21
23
  * @param {number} Options.options.maxRetries [param=1] number of max retries, default 1
22
24
  * @param {number} Options.options.maxTimeout [param=86400000] max timeout, in ms, default 86400000ms(1d)
23
25
  * @param {number} Options.options.retryDelay [param=0] retry delay, in ms, default 0ms
26
+ * @param {boolean} Options.options.enableScheduledJob [param=false] enable scheduled job or not, default is false
24
27
  */
25
28
  module.exports = function createQueue({ file, onJob, options = {} }) {
26
29
  if (typeof onJob !== 'function') {
@@ -38,6 +41,7 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
38
41
  const concurrency = Math.max(options.concurrency || defaults.concurrency, 1);
39
42
  const maxTimeout = Math.max(options.maxTimeout || defaults.maxTimeout, 0);
40
43
  const retryDelay = Math.max(options.retryDelay || defaults.retryDelay, 0);
44
+ const enableScheduledJob = typeof options.enableScheduledJob === 'boolean' ? options.enableScheduledJob : false;
41
45
 
42
46
  if (typeof options.maxRetries === 'number' && options.maxRetries >= 0) {
43
47
  maxRetries = options.maxRetries;
@@ -46,6 +50,9 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
46
50
  const store = new JobStore(file);
47
51
  const queueEvents = new EventEmitter();
48
52
 
53
+ const getJobId = (jobId, job) =>
54
+ jobId || (typeof options.id === 'function' ? options.id(job) : uuid.v4()) || uuid.v4();
55
+
49
56
  const queue = Queue(async ({ job, id, persist }, cb) => {
50
57
  if (persist) {
51
58
  try {
@@ -68,7 +75,32 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
68
75
  }
69
76
  }, concurrency);
70
77
 
71
- const push = (job, jobId, persist = true) => {
78
+ /**
79
+ * Push job to the queue, the old way of calling `push(job, jobId, persist)` will be deprecated
80
+ * @param {object} args
81
+ * @param {object} args.job The data of the job
82
+ * @param {string} args.jobId Optional, custom jobId
83
+ * @param {boolean} args.persist [persist=true] Persisting the job to the database
84
+ * @param {number} args.delay Optional, default with no delay, unit is second, for example: 10 will run after 10s
85
+ * @returns
86
+ */
87
+ const push = (...args) => {
88
+ let job;
89
+ let jobId;
90
+ let persist;
91
+ let delay;
92
+
93
+ if (
94
+ args.length === 1 &&
95
+ args[0] &&
96
+ typeof args[0] === 'object' &&
97
+ (args[0].job || args[0].jobId || args[0].persist)
98
+ ) {
99
+ [{ job, jobId, persist = true, delay }] = args;
100
+ } else {
101
+ [job, jobId, persist = true] = args;
102
+ }
103
+
72
104
  const jobEvents = new EventEmitter();
73
105
  const emit = (e, data) => {
74
106
  queueEvents.emit(e, data);
@@ -79,10 +111,46 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
79
111
  throw new Error('Can not queue empty job');
80
112
  }
81
113
 
82
- const id = jobId || (typeof options.id === 'function' ? options.id(job) : uuid.v4()) || uuid.v4();
114
+ const id = getJobId(jobId, job);
115
+
116
+ if (delay) {
117
+ if (!enableScheduledJob) {
118
+ throw new Error('The queue must have options.enableScheduledJob set to true to run delay jobs');
119
+ }
120
+
121
+ // 这里不是精确的 delay, 延迟的时间太短没有意义,所以这里限制了最小 delay
122
+ if (delay < MIN_DELAY) {
123
+ throw new Error(`minimum delay is ${MIN_DELAY}s`);
124
+ }
125
+
126
+ store.addJob(
127
+ id,
128
+ job,
129
+ (err) => {
130
+ if (err) {
131
+ logger.error('failed to add job', err);
132
+ emit('failed', { id, job, error: err });
133
+ return;
134
+ }
135
+
136
+ emit('queued', { id, job });
137
+ },
138
+ { delay, willRunAt: Date.now() + delay * 1000 }
139
+ );
140
+
141
+ jobEvents.id = id;
142
+
143
+ return jobEvents;
144
+ }
145
+
83
146
  // eslint-disable-next-line no-shadow
84
- const clearJob = (id) => {
147
+ const clearJob = (id, cb) => {
85
148
  store.deleteJob(id, (e) => {
149
+ if (typeof cb === 'function') {
150
+ cb(e);
151
+ return;
152
+ }
153
+
86
154
  if (e) {
87
155
  logger.error('Failed to delete job from persistent store', { error: e, id });
88
156
  }
@@ -111,8 +179,9 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
111
179
 
112
180
  if (attrs.retryCount >= maxRetries) {
113
181
  logger.info('fail job', { id });
114
- emit('failed', { id, job, error: err });
115
- clearJob(id);
182
+ clearJob(id, () => {
183
+ emit('failed', { id, job, error: err });
184
+ });
116
185
  return;
117
186
  }
118
187
 
@@ -205,6 +274,26 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
205
274
  });
206
275
  });
207
276
 
277
+ const loop = async () => {
278
+ if (enableScheduledJob === true) {
279
+ while (true) {
280
+ /* eslint-disable no-await-in-loop */
281
+ const jobs = await store.getScheduledJobs();
282
+ jobs.forEach((x) => {
283
+ if (x.job && x._id) {
284
+ push(x.job, x._id, false);
285
+ } else {
286
+ logger.info('skip invalid job from db', { job: x });
287
+ }
288
+ });
289
+
290
+ await sleep(2000);
291
+ }
292
+ }
293
+ };
294
+
295
+ loop();
296
+
208
297
  return Object.assign(queueEvents, {
209
298
  store,
210
299
  push,
@@ -215,6 +304,7 @@ module.exports = function createQueue({ file, onJob, options = {} }) {
215
304
  maxRetries,
216
305
  maxTimeout,
217
306
  retryDelay,
307
+ enableScheduledJob,
218
308
  },
219
309
  });
220
310
  };
package/lib/store.js CHANGED
@@ -27,7 +27,7 @@ class JobStore {
27
27
 
28
28
  getJobs(cb) {
29
29
  this.db
30
- .cursor({})
30
+ .cursor({ delay: { $exists: false } })
31
31
  .sort({ createdAt: 1 })
32
32
  .exec((err, result) => {
33
33
  if (err) {
@@ -37,6 +37,13 @@ class JobStore {
37
37
  });
38
38
  }
39
39
 
40
+ getScheduledJobs() {
41
+ return this.db
42
+ .cursor({ delay: { $exists: true }, willRunAt: { $lte: Date.now() } })
43
+ .sort({ createdAt: 1 })
44
+ .exec();
45
+ }
46
+
40
47
  updateJob(id, updates, cb) {
41
48
  this.db.findOne({ _id: id }, (err, exist) => {
42
49
  if (err) return cb(err);
@@ -55,7 +62,7 @@ class JobStore {
55
62
  },
56
63
  (e) => {
57
64
  if (e) {
58
- console.error('error updating job', err);
65
+ console.error('error updating job', e);
59
66
  return cb(e, null);
60
67
  }
61
68
  return cb(null, Object.assign(exist, update));
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.8.20",
6
+ "version": "1.8.22",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,13 +19,13 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@abtnode/db": "1.8.20",
23
- "@abtnode/util": "1.8.20",
22
+ "@abtnode/db": "1.8.22",
23
+ "@abtnode/util": "1.8.22",
24
24
  "debug": "^4.3.4",
25
25
  "fastq": "^1.13.0",
26
26
  "uuid": "7.0.3"
27
27
  },
28
- "gitHead": "abe47e26c9583bfe5c4969e19cd36574f436a515",
28
+ "gitHead": "b0b31c2a0d29de79b29591f020f577d6c3806248",
29
29
  "devDependencies": {
30
30
  "jest": "^27.5.1"
31
31
  }