Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`\
Expand Down Expand Up @@ -439,6 +441,8 @@ queue.timeout = 10000;

#### [.concurrency](#concurrency)

#### [.intervalCap](#intervalcap)

#### .isPaused

Whether the queue is currently paused.
Expand Down
30 changes: 27 additions & 3 deletions source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ Promise queue with concurrency control.
export default class PQueue<QueueType extends Queue<RunFunction, EnqueueOptionsType> = PriorityQueue, EnqueueOptionsType extends QueueAddOptions = QueueAddOptions> extends EventEmitter<EventName> { // 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;
Expand Down Expand Up @@ -119,10 +120,10 @@ export default class PQueue<QueueType extends Queue<RunFunction, EnqueueOptionsT
// TODO: Remove this fallback in the next major version
// eslint-disable-next-line @typescript-eslint/no-deprecated
this.#carryoverIntervalCount = options.carryoverIntervalCount ?? options.carryoverConcurrencyCount ?? false;
this.#isIntervalIgnored = options.intervalCap === Number.POSITIVE_INFINITY || options.interval === 0;
this.#intervalCap = options.intervalCap;
this.#interval = options.interval;
this.#strict = options.strict!;
this.#updateIsIntervalIgnored();
this.#queue = new options.queueClass!();
this.#queueClass = options.queueClass!;
this.concurrency = options.concurrency!;
Expand Down Expand Up @@ -390,6 +391,29 @@ export default class PQueue<QueueType extends Queue<RunFunction, EnqueueOptionsT
this.#processQueue();
}

#updateIsIntervalIgnored(): void {
this.#isIntervalIgnored = this.#intervalCap === Number.POSITIVE_INFINITY || this.#interval === 0;
}

get intervalCap(): number {
return this.#intervalCap;
}

set intervalCap(newIntervalCap: number) {
if (!(typeof newIntervalCap === 'number' && newIntervalCap >= 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.

Expand Down
59 changes: 59 additions & 0 deletions test/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
() => {
Expand Down
Loading