UNPKG

18 kBJavaScriptView Raw
1"use strict";
2
3function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
4
5function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
6
7function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _nonIterableRest(); }
8
9function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
10
11function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
12
13function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
14
15function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
16
17function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
18
19var Bottleneck,
20 DEFAULT_PRIORITY,
21 Events,
22 Job,
23 LocalDatastore,
24 NUM_PRIORITIES,
25 Queues,
26 RedisDatastore,
27 States,
28 Sync,
29 parser,
30 splice = [].splice;
31NUM_PRIORITIES = 10;
32DEFAULT_PRIORITY = 5;
33parser = require("./parser");
34Queues = require("./Queues");
35Job = require("./Job");
36LocalDatastore = require("./LocalDatastore");
37RedisDatastore = require("./RedisDatastore");
38Events = require("./Events");
39States = require("./States");
40Sync = require("./Sync");
41
42Bottleneck = function () {
43 class Bottleneck {
44 constructor(options = {}, ...invalid) {
45 var storeInstanceOptions, storeOptions;
46 this._addToQueue = this._addToQueue.bind(this);
47
48 this._validateOptions(options, invalid);
49
50 parser.load(options, this.instanceDefaults, this);
51 this._queues = new Queues(NUM_PRIORITIES);
52 this._scheduled = {};
53 this._states = new States(["RECEIVED", "QUEUED", "RUNNING", "EXECUTING"].concat(this.trackDoneStatus ? ["DONE"] : []));
54 this._limiter = null;
55 this.Events = new Events(this);
56 this._submitLock = new Sync("submit", this.Promise);
57 this._registerLock = new Sync("register", this.Promise);
58 storeOptions = parser.load(options, this.storeDefaults, {});
59
60 this._store = function () {
61 if (this.datastore === "redis" || this.datastore === "ioredis" || this.connection != null) {
62 storeInstanceOptions = parser.load(options, this.redisStoreDefaults, {});
63 return new RedisDatastore(this, storeOptions, storeInstanceOptions);
64 } else if (this.datastore === "local") {
65 storeInstanceOptions = parser.load(options, this.localStoreDefaults, {});
66 return new LocalDatastore(this, storeOptions, storeInstanceOptions);
67 } else {
68 throw new Bottleneck.prototype.BottleneckError(`Invalid datastore type: ${this.datastore}`);
69 }
70 }.call(this);
71
72 this._queues.on("leftzero", () => {
73 var ref;
74 return (ref = this._store.heartbeat) != null ? typeof ref.ref === "function" ? ref.ref() : void 0 : void 0;
75 });
76
77 this._queues.on("zero", () => {
78 var ref;
79 return (ref = this._store.heartbeat) != null ? typeof ref.unref === "function" ? ref.unref() : void 0 : void 0;
80 });
81 }
82
83 _validateOptions(options, invalid) {
84 if (!(options != null && typeof options === "object" && invalid.length === 0)) {
85 throw new Bottleneck.prototype.BottleneckError("Bottleneck v2 takes a single object argument. Refer to https://github.com/SGrondin/bottleneck#upgrading-to-v2 if you're upgrading from Bottleneck v1.");
86 }
87 }
88
89 ready() {
90 return this._store.ready;
91 }
92
93 clients() {
94 return this._store.clients;
95 }
96
97 channel() {
98 return `b_${this.id}`;
99 }
100
101 channel_client() {
102 return `b_${this.id}_${this._store.clientId}`;
103 }
104
105 publish(message) {
106 return this._store.__publish__(message);
107 }
108
109 disconnect(flush = true) {
110 return this._store.__disconnect__(flush);
111 }
112
113 chain(_limiter) {
114 this._limiter = _limiter;
115 return this;
116 }
117
118 queued(priority) {
119 return this._queues.queued(priority);
120 }
121
122 clusterQueued() {
123 return this._store.__queued__();
124 }
125
126 empty() {
127 return this.queued() === 0 && this._submitLock.isEmpty();
128 }
129
130 running() {
131 return this._store.__running__();
132 }
133
134 done() {
135 return this._store.__done__();
136 }
137
138 jobStatus(id) {
139 return this._states.jobStatus(id);
140 }
141
142 jobs(status) {
143 return this._states.statusJobs(status);
144 }
145
146 counts() {
147 return this._states.statusCounts();
148 }
149
150 _randomIndex() {
151 return Math.random().toString(36).slice(2);
152 }
153
154 check(weight = 1) {
155 return this._store.__check__(weight);
156 }
157
158 _clearGlobalState(index) {
159 if (this._scheduled[index] != null) {
160 clearTimeout(this._scheduled[index].expiration);
161 delete this._scheduled[index];
162 return true;
163 } else {
164 return false;
165 }
166 }
167
168 _free(index, job, options, eventInfo) {
169 var _this = this;
170
171 return _asyncToGenerator(function* () {
172 var e, running;
173
174 try {
175 var _ref = yield _this._store.__free__(index, options.weight);
176
177 running = _ref.running;
178
179 _this.Events.trigger("debug", `Freed ${options.id}`, eventInfo);
180
181 if (running === 0 && _this.empty()) {
182 return _this.Events.trigger("idle");
183 }
184 } catch (error1) {
185 e = error1;
186 return _this.Events.trigger("error", e);
187 }
188 })();
189 }
190
191 _run(index, job, wait) {
192 var clearGlobalState, free, run;
193 job.doRun();
194 clearGlobalState = this._clearGlobalState.bind(this, index);
195 run = this._run.bind(this, index, job);
196 free = this._free.bind(this, index, job);
197 return this._scheduled[index] = {
198 timeout: setTimeout(() => {
199 return job.doExecute(this._limiter, clearGlobalState, run, free);
200 }, wait),
201 expiration: job.options.expiration != null ? setTimeout(function () {
202 return job.doExpire(clearGlobalState, run, free);
203 }, wait + job.options.expiration) : void 0,
204 job: job
205 };
206 }
207
208 _drainOne(capacity) {
209 return this._registerLock.schedule(() => {
210 var args, index, next, options, queue;
211
212 if (this.queued() === 0) {
213 return this.Promise.resolve(null);
214 }
215
216 queue = this._queues.getFirst();
217
218 var _next2 = next = queue.first();
219
220 options = _next2.options;
221 args = _next2.args;
222
223 if (capacity != null && options.weight > capacity) {
224 return this.Promise.resolve(null);
225 }
226
227 this.Events.trigger("debug", `Draining ${options.id}`, {
228 args,
229 options
230 });
231 index = this._randomIndex();
232 return this._store.__register__(index, options.weight, options.expiration).then(({
233 success,
234 wait,
235 reservoir
236 }) => {
237 var empty;
238 this.Events.trigger("debug", `Drained ${options.id}`, {
239 success,
240 args,
241 options
242 });
243
244 if (success) {
245 queue.shift();
246 empty = this.empty();
247
248 if (empty) {
249 this.Events.trigger("empty");
250 }
251
252 if (reservoir === 0) {
253 this.Events.trigger("depleted", empty);
254 }
255
256 this._run(index, next, wait);
257
258 return this.Promise.resolve(options.weight);
259 } else {
260 return this.Promise.resolve(null);
261 }
262 });
263 });
264 }
265
266 _drainAll(capacity, total = 0) {
267 return this._drainOne(capacity).then(drained => {
268 var newCapacity;
269
270 if (drained != null) {
271 newCapacity = capacity != null ? capacity - drained : capacity;
272 return this._drainAll(newCapacity, total + drained);
273 } else {
274 return this.Promise.resolve(total);
275 }
276 }).catch(e => {
277 return this.Events.trigger("error", e);
278 });
279 }
280
281 _dropAllQueued(message) {
282 return this._queues.shiftAll(function (job) {
283 return job.doDrop({
284 message
285 });
286 });
287 }
288
289 stop(options = {}) {
290 var done, waitForExecuting;
291 options = parser.load(options, this.stopDefaults);
292
293 waitForExecuting = at => {
294 var finished;
295
296 finished = () => {
297 var counts;
298 counts = this._states.counts;
299 return counts[0] + counts[1] + counts[2] + counts[3] === at;
300 };
301
302 return new this.Promise((resolve, reject) => {
303 if (finished()) {
304 return resolve();
305 } else {
306 return this.on("done", () => {
307 if (finished()) {
308 this.removeAllListeners("done");
309 return resolve();
310 }
311 });
312 }
313 });
314 };
315
316 done = options.dropWaitingJobs ? (this._run = function (index, next) {
317 return next.doDrop({
318 message: options.dropErrorMessage
319 });
320 }, this._drainOne = () => {
321 return this.Promise.resolve(null);
322 }, this._registerLock.schedule(() => {
323 return this._submitLock.schedule(() => {
324 var k, ref, v;
325 ref = this._scheduled;
326
327 for (k in ref) {
328 v = ref[k];
329
330 if (this.jobStatus(v.job.options.id) === "RUNNING") {
331 clearTimeout(v.timeout);
332 clearTimeout(v.expiration);
333 v.job.doDrop({
334 message: options.dropErrorMessage
335 });
336 }
337 }
338
339 this._dropAllQueued(options.dropErrorMessage);
340
341 return waitForExecuting(0);
342 });
343 })) : this.schedule({
344 priority: NUM_PRIORITIES - 1,
345 weight: 0
346 }, () => {
347 return waitForExecuting(1);
348 });
349
350 this._receive = function (job) {
351 return job._reject(new Bottleneck.prototype.BottleneckError(options.enqueueErrorMessage));
352 };
353
354 this.stop = () => {
355 return this.Promise.reject(new Bottleneck.prototype.BottleneckError("stop() has already been called"));
356 };
357
358 return done;
359 }
360
361 _addToQueue(job) {
362 var _this2 = this;
363
364 return _asyncToGenerator(function* () {
365 var args, blocked, error, options, reachedHWM, shifted, strategy;
366 args = job.args;
367 options = job.options;
368
369 try {
370 var _ref2 = yield _this2._store.__submit__(_this2.queued(), options.weight);
371
372 reachedHWM = _ref2.reachedHWM;
373 blocked = _ref2.blocked;
374 strategy = _ref2.strategy;
375 } catch (error1) {
376 error = error1;
377
378 _this2.Events.trigger("debug", `Could not queue ${options.id}`, {
379 args,
380 options,
381 error
382 });
383
384 job.doDrop({
385 error
386 });
387 return false;
388 }
389
390 if (blocked) {
391 job.doDrop();
392 return true;
393 } else if (reachedHWM) {
394 shifted = strategy === Bottleneck.prototype.strategy.LEAK ? _this2._queues.shiftLastFrom(options.priority) : strategy === Bottleneck.prototype.strategy.OVERFLOW_PRIORITY ? _this2._queues.shiftLastFrom(options.priority + 1) : strategy === Bottleneck.prototype.strategy.OVERFLOW ? job : void 0;
395
396 if (shifted != null) {
397 shifted.doDrop();
398 }
399
400 if (shifted == null || strategy === Bottleneck.prototype.strategy.OVERFLOW) {
401 if (shifted == null) {
402 job.doDrop();
403 }
404
405 return reachedHWM;
406 }
407 }
408
409 job.doQueue(reachedHWM, blocked);
410
411 _this2._queues.push(job);
412
413 yield _this2._drainAll();
414 return reachedHWM;
415 })();
416 }
417
418 _receive(job) {
419 if (this._states.jobStatus(job.options.id) != null) {
420 job._reject(new Bottleneck.prototype.BottleneckError(`A job with the same id already exists (id=${job.options.id})`));
421
422 return false;
423 } else {
424 job.doReceive();
425 return this._submitLock.schedule(this._addToQueue, job);
426 }
427 }
428
429 submit(...args) {
430 var cb, fn, job, options, ref, ref1, task;
431
432 if (typeof args[0] === "function") {
433 var _ref3, _ref4, _splice$call, _splice$call2;
434
435 ref = args, (_ref3 = ref, _ref4 = _toArray(_ref3), fn = _ref4[0], args = _ref4.slice(1), _ref3), (_splice$call = splice.call(args, -1), _splice$call2 = _slicedToArray(_splice$call, 1), cb = _splice$call2[0], _splice$call);
436 options = parser.load({}, this.jobDefaults);
437 } else {
438 var _ref5, _ref6, _splice$call3, _splice$call4;
439
440 ref1 = args, (_ref5 = ref1, _ref6 = _toArray(_ref5), options = _ref6[0], fn = _ref6[1], args = _ref6.slice(2), _ref5), (_splice$call3 = splice.call(args, -1), _splice$call4 = _slicedToArray(_splice$call3, 1), cb = _splice$call4[0], _splice$call3);
441 options = parser.load(options, this.jobDefaults);
442 }
443
444 task = (...args) => {
445 return new this.Promise(function (resolve, reject) {
446 return fn(...args, function (...args) {
447 return (args[0] != null ? reject : resolve)(args);
448 });
449 });
450 };
451
452 job = new Job(task, args, options, this.jobDefaults, this.rejectOnDrop, this.Events, this._states, this.Promise);
453 job.promise.then(function (args) {
454 return typeof cb === "function" ? cb(...args) : void 0;
455 }).catch(function (args) {
456 if (Array.isArray(args)) {
457 return typeof cb === "function" ? cb(...args) : void 0;
458 } else {
459 return typeof cb === "function" ? cb(args) : void 0;
460 }
461 });
462 return this._receive(job);
463 }
464
465 schedule(...args) {
466 var job, options, task;
467
468 if (typeof args[0] === "function") {
469 var _args = args;
470
471 var _args2 = _toArray(_args);
472
473 task = _args2[0];
474 args = _args2.slice(1);
475 options = {};
476 } else {
477 var _args3 = args;
478
479 var _args4 = _toArray(_args3);
480
481 options = _args4[0];
482 task = _args4[1];
483 args = _args4.slice(2);
484 }
485
486 job = new Job(task, args, options, this.jobDefaults, this.rejectOnDrop, this.Events, this._states, this.Promise);
487
488 this._receive(job);
489
490 return job.promise;
491 }
492
493 wrap(fn) {
494 var schedule, wrapped;
495 schedule = this.schedule.bind(this);
496
497 wrapped = function wrapped(...args) {
498 return schedule(fn.bind(this), ...args);
499 };
500
501 wrapped.withOptions = function (options, ...args) {
502 return schedule(options, fn, ...args);
503 };
504
505 return wrapped;
506 }
507
508 updateSettings(options = {}) {
509 var _this3 = this;
510
511 return _asyncToGenerator(function* () {
512 yield _this3._store.__updateSettings__(parser.overwrite(options, _this3.storeDefaults));
513 parser.overwrite(options, _this3.instanceDefaults, _this3);
514 return _this3;
515 })();
516 }
517
518 currentReservoir() {
519 return this._store.__currentReservoir__();
520 }
521
522 incrementReservoir(incr = 0) {
523 return this._store.__incrementReservoir__(incr);
524 }
525
526 }
527
528 ;
529 Bottleneck.default = Bottleneck;
530 Bottleneck.Events = Events;
531 Bottleneck.version = Bottleneck.prototype.version = require("./version.json").version;
532 Bottleneck.strategy = Bottleneck.prototype.strategy = {
533 LEAK: 1,
534 OVERFLOW: 2,
535 OVERFLOW_PRIORITY: 4,
536 BLOCK: 3
537 };
538 Bottleneck.BottleneckError = Bottleneck.prototype.BottleneckError = require("./BottleneckError");
539 Bottleneck.Group = Bottleneck.prototype.Group = require("./Group");
540 Bottleneck.RedisConnection = Bottleneck.prototype.RedisConnection = require("./RedisConnection");
541 Bottleneck.IORedisConnection = Bottleneck.prototype.IORedisConnection = require("./IORedisConnection");
542 Bottleneck.Batcher = Bottleneck.prototype.Batcher = require("./Batcher");
543 Bottleneck.prototype.jobDefaults = {
544 priority: DEFAULT_PRIORITY,
545 weight: 1,
546 expiration: null,
547 id: "<no-id>"
548 };
549 Bottleneck.prototype.storeDefaults = {
550 maxConcurrent: null,
551 minTime: 0,
552 highWater: null,
553 strategy: Bottleneck.prototype.strategy.LEAK,
554 penalty: null,
555 reservoir: null,
556 reservoirRefreshInterval: null,
557 reservoirRefreshAmount: null,
558 reservoirIncreaseInterval: null,
559 reservoirIncreaseAmount: null,
560 reservoirIncreaseMaximum: null
561 };
562 Bottleneck.prototype.localStoreDefaults = {
563 Promise: Promise,
564 timeout: null,
565 heartbeatInterval: 250
566 };
567 Bottleneck.prototype.redisStoreDefaults = {
568 Promise: Promise,
569 timeout: null,
570 heartbeatInterval: 5000,
571 clientTimeout: 10000,
572 Redis: null,
573 clientOptions: {},
574 clusterNodes: null,
575 clearDatastore: false,
576 connection: null
577 };
578 Bottleneck.prototype.instanceDefaults = {
579 datastore: "local",
580 connection: null,
581 id: "<no-id>",
582 rejectOnDrop: true,
583 trackDoneStatus: false,
584 Promise: Promise
585 };
586 Bottleneck.prototype.stopDefaults = {
587 enqueueErrorMessage: "This limiter has been stopped and cannot accept new jobs.",
588 dropWaitingJobs: true,
589 dropErrorMessage: "This limiter has been stopped."
590 };
591 return Bottleneck;
592}.call(void 0);
593
594module.exports = Bottleneck;
\No newline at end of file