@byloth/core
Version:
An unopinionated collection of useful functions and classes that I use widely in all my projects. 🔧
224 lines (203 loc) • 7.49 kB
text/typescript
import type { Callback } from "../callbacks/types.js";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { TimeoutException, ValueException } from "../exceptions/index.js";
import DeferredPromise from "./deferred-promise.js";
import SmartPromise from "./smart-promise.js";
import TimedPromise from "./timed-promise.js";
import type { MaybePromise, PromiseRejecter, PromiseResolver } from "./types.js";
/**
* A class that represents a queue of asynchronous operations, allowing them to be executed sequentially.
*
* It extends the {@link SmartPromise} class, providing a way to manage multiple promises in a controlled manner.
* This class is useful for scenarios where you need to ensure
* that only one asynchronous operation is executed at a time,
* such as when dealing with API requests, file operations or any other
* asynchronous tasks that need to be handled in a specific order.
*
* ---
*
* @example
* ```ts
* const queue = new PromiseQueue();
*
* queue.enqueue(() => new Promise((resolve) => setTimeout(() => resolve("First"), 2000)))
* queue.enqueue(() => new Promise((resolve) => setTimeout(() => resolve("Second"), 500)))
* queue.enqueue(() => new Promise((resolve) => setTimeout(() => resolve("Third"), 1000)))
*
* await queue; // "First", "Second", "Third"
* ```
*/
export default class PromiseQueue extends SmartPromise<void>
{
/**
* The number of promises currently in the queue.
*/
protected _count: number;
/**
* A flag indicating whether the promise is still pending or not.
*/
public override get isPending(): boolean
{
return this._count > 0;
}
/**
* A flag indicating whether the promise has been fulfilled or not.
*/
public override get isFulfilled(): boolean
{
return this._count === 0;
}
/**
* A flag indicating whether the promise has been rejected or not.
*
* Please note the {@link PromiseQueue} doesn't support rejection states.
* Accessing this property will always result in a {@link ValueException}.
*/
public override get isRejected(): never
{
throw new ValueException("`PromiseQueue` doesn't support rejection states.");
}
/**
* The native {@link Promise} object wrapped by this instance.
*/
declare protected _promise: Promise<void>;
/**
* Initializes a new instance of the {@link PromiseQueue} class.
*/
public constructor()
{
super((resolve) => resolve());
this._count = 0;
this._isPending = false;
this._isFulfilled = false;
this._isRejected = false;
}
/**
* Enqueues a {@link DeferredPromise} into the queue.
*
* The promise will be executed in sequence after previously enqueued promises.
*
* ---
*
* @example
* ```ts
* const queue = new PromiseQueue();
* const deferred = new DeferredPromise(() => console.log("Hello, world!"));
*
* queue.enqueue(deferred); // "Hello, world!"
* ```
*
* ---
*
* @template T The type of value the promise will eventually resolve to.
*
* @param promise A `DeferredPromise<void, T>` instance to enqueue.
*
* @returns A {@link SmartPromise} that resolves to the value of the enqueued promise.
*/
public enqueue<T>(promise: DeferredPromise<void, T>): SmartPromise<T>;
/**
* Enqueues a {@link DeferredPromise} into the queue with an optional timeout.
*
* The promise will be executed in sequence after previously enqueued promises.
* If the promise takes longer than the specified timeout, it will be rejected with a {@link TimeoutException}.
*
* ---
*
* @example
* ```ts
* const queue = new PromiseQueue();
* const deferred = new DeferredPromise(() => console.log("Hello, world!"));
*
* queue.enqueue(deferred, 5000); // "Hello, world!"
* ```
*
* ---
*
* @template T The type of value the promise will eventually resolve to.
*
* @param promise A `DeferredPromise<void, T>` instance to enqueue.
* @param timeout The maximum time in milliseconds that the operation can take before timing out.
*
* @returns
* A {@link TimedPromise} that resolves to the value of the enqueued promise or rejects
* with a `TimeoutException` if the operation takes longer than the specified timeout.
*/
public enqueue<T>(promise: DeferredPromise<void, T>, timeout: number): TimedPromise<T>;
/**
* Enqueues a callback that returns a {@link MaybePromise} value of type `T` into the queue.
*
* The executor will be executed in sequence after previously enqueued promises.
*
* ---
*
* @example
* ```ts
* const queue = new PromiseQueue();
*
* queue.enqueue(() => console.log("Hello, world!")); // "Hello, world!"
* ```
*
* ---
*
* @template T The type of value the promise will eventually resolve to.
*
* @param executor A callback that returns a `MaybePromise<T>` value to enqueue.
*
* @returns A {@link SmartPromise} that resolves to the value of the enqueued executor.
*/
public enqueue<T>(executor: Callback<[], MaybePromise<T>>): SmartPromise<T>;
/**
* Enqueues a callback that returns a {@link MaybePromise}
* value of type `T` into the queue with an optional timeout.
*
* The executor will be executed in sequence after previously enqueued promises.
* If the executor takes longer than the specified timeout, it will be rejected with a {@link TimeoutException}.
*
* ---
*
* @example
* ```ts
* const queue = new PromiseQueue();
*
* queue.enqueue(() => console.log("Hello, world!"), 5000); // "Hello, world!"
* ```
*
* ---
*
* @template T The type of value the promise will eventually resolve to.
*
* @param executor A callback that returns a `MaybePromise<T>` value to enqueue.
* @param timeout The maximum time in milliseconds that the operation can take before timing out.
*
* @returns
* A {@link TimedPromise} that resolves to the value of the enqueued executor or rejects
* with a `TimeoutException` if the operation takes longer than the specified timeout.
*/
public enqueue<T>(executor: Callback<[], MaybePromise<T>>, timeout?: number): TimedPromise<T>;
public enqueue<T>(
executor: DeferredPromise<void, T> | Callback<[], MaybePromise<T>>, timeout?: number
): SmartPromise<T> | TimedPromise<T>
{
this._count += 1;
if (executor instanceof DeferredPromise)
{
const _executor = executor as DeferredPromise<void, T>;
executor = () =>
{
_executor.resolve();
return _executor;
};
}
const _executor = (resolve: PromiseResolver<T>, reject: PromiseRejecter) =>
{
this._promise = this._promise
.then(executor)
.then((value) => { this._count -= 1; resolve(value); })
.catch((value) => { this._count -= 1; reject(value); });
};
if (timeout) { return new TimedPromise<T>(_executor, timeout); }
return new SmartPromise<T>(_executor);
}
public override readonly [Symbol.toStringTag]: string = "PromiseQueue";
}