diff --git a/readme.md b/readme.md index c011e6c..1750946 100644 --- a/readme.md +++ b/readme.md @@ -125,6 +125,8 @@ Minimum: `1` The max number of runs in the given interval of time. +Can be changed after the queue is created via `queue.intervalCap`. + ##### interval Type: `number`\ @@ -439,6 +441,8 @@ queue.timeout = 10000; #### [.concurrency](#concurrency) +#### [.intervalCap](#intervalcap) + #### .isPaused Whether the queue is currently paused. diff --git a/source/index.ts b/source/index.ts index bf7978c..9bfcac3 100644 --- a/source/index.ts +++ b/source/index.ts @@ -16,11 +16,12 @@ Promise queue with concurrency control. export default class PQueue = PriorityQueue, EnqueueOptionsType extends QueueAddOptions = QueueAddOptions> extends EventEmitter { // eslint-disable-line @typescript-eslint/naming-convention readonly #carryoverIntervalCount: boolean; - readonly #isIntervalIgnored: boolean; + // The `!` is needed because it's assigned via `#updateIsIntervalIgnored()` in the constructor. + #isIntervalIgnored!: boolean; #intervalCount = 0; - readonly #intervalCap: number; + #intervalCap: number; #rateLimitedInInterval = false; #rateLimitFlushScheduled = false; @@ -119,10 +120,10 @@ export default class PQueue= 1)) { + throw new TypeError(`Expected \`intervalCap\` to be a number from 1 and up, got \`${newIntervalCap}\` (${typeof newIntervalCap})`); + } + + if (this.#strict && newIntervalCap === Number.POSITIVE_INFINITY) { + throw new TypeError('The `strict` option requires a finite `intervalCap`'); + } + + this.#intervalCap = newIntervalCap; + this.#updateIsIntervalIgnored(); + + this.#processQueue(); + } + /** Updates the priority of a promise function by its id, affecting its execution order. Requires a defined concurrency limit to take effect. diff --git a/test/basic.ts b/test/basic.ts index 1e90246..02af4c8 100644 --- a/test/basic.ts +++ b/test/basic.ts @@ -899,6 +899,65 @@ test('enforce number in options.intervalCap', () => { }); }); +test('enforce number in queue.intervalCap', () => { + assert.throws( + () => { + (new PQueue()).intervalCap = 0; + }, + {constructor: TypeError}, + ); + + assert.throws( + () => { + // @ts-expect-error Testing + (new PQueue()).intervalCap = undefined; + }, + {constructor: TypeError}, + ); + + assert.doesNotThrow(() => { + (new PQueue()).intervalCap = 1; + }); + + assert.doesNotThrow(() => { + (new PQueue()).intervalCap = 10; + }); + + assert.doesNotThrow(() => { + (new PQueue()).intervalCap = Number.POSITIVE_INFINITY; + }); +}); + +test('queue.intervalCap requires a finite value when strict', () => { + const queue = new PQueue({intervalCap: 1, interval: 100, strict: true}); + + assert.throws( + () => { + queue.intervalCap = Number.POSITIVE_INFINITY; + }, + {constructor: TypeError}, + ); +}); + +test('queue.intervalCap can be changed at runtime', async () => { + const queue = new PQueue({intervalCap: 1, interval: 100}); + + for (let index = 0; index < 4; index++) { + queue.add(async () => delay(1000)); + } + + // Only `intervalCap` (1) tasks are dequeued up front. + assert.equal(queue.pending, 1); + assert.equal(queue.size, 3); + + // Raising the cap should immediately let more tasks start, without + // waiting for another interval tick. + queue.intervalCap = 3; + + assert.equal(queue.pending, 3); + assert.equal(queue.size, 1); +}); + test('enforce finite in options.interval', () => { assert.throws( () => {