Files
suwayomi-material-you-webui/src/lib/Queue.ts
2026-03-11 19:42:02 +01:00

95 lines
2.9 KiB
TypeScript

/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import type { LimitFunction } from 'p-limit';
import pLimit from 'p-limit';
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
export enum Priority {
LOW = 0,
NORMAL = 1,
HIGH = 2,
}
export type QueuePriority = Priority | number;
type Key = string;
type QueueItemFunction<T = any> = () => PromiseLike<T> | T;
export class Queue {
private readonly queue: LimitFunction;
private counter: number = 0;
private pendingKeyToPriorityMap = new Map<Key, QueuePriority>();
private pendingKeyToFnMap = new Map<Key, QueueItemFunction>();
private pendingKeyToPromiseMap = new Map<Key, ControlledPromise<any>>();
constructor(concurrency: number) {
this.queue = pLimit(concurrency);
}
clear(): void {
this.pendingKeyToPromiseMap.forEach((promise) => {
promise.reject(new Error('Queue::clear: called'));
});
this.pendingKeyToPriorityMap.clear();
this.pendingKeyToFnMap.clear();
this.pendingKeyToPromiseMap.clear();
}
enqueue<T>(
key: Key,
fn: () => PromiseLike<T> | T,
priority: QueuePriority = Priority.NORMAL,
): { key: string; promise: Promise<T> } {
this.counter = (this.counter + 1) % Number.MAX_SAFE_INTEGER;
const actualKey = `${key}_${this.counter}`;
this.pendingKeyToPriorityMap.set(actualKey, priority);
this.pendingKeyToFnMap.set(actualKey, fn);
const processPromise = new ControlledPromise<T>();
this.pendingKeyToPromiseMap.set(actualKey, processPromise);
this.queue(() => this.process());
return { key: actualKey, promise: processPromise.promise };
}
isProcessing(key: string): boolean {
return !this.pendingKeyToFnMap.has(key);
}
private async process(): Promise<void> {
const { fn, promise } = this.getNextItemToProcess();
try {
const result = await fn();
promise.resolve(result);
} catch (e) {
promise.reject(e);
}
}
private getNextItemToProcess<T>(): { key: Key; fn: QueueItemFunction<T>; promise: ControlledPromise<T> } {
const [key] = [...this.pendingKeyToPriorityMap.entries()].toSorted(
([, priorityA], [, priorityB]) => priorityB - priorityA,
)[0];
const fn = this.pendingKeyToFnMap.get(key) as () => PromiseLike<T> | T;
const promise = this.pendingKeyToPromiseMap.get(key) as ControlledPromise<T>;
this.pendingKeyToPriorityMap.delete(key);
this.pendingKeyToFnMap.delete(key);
this.pendingKeyToPromiseMap.delete(key);
return { key, fn, promise };
}
}