UNPKG

@byloth/core

Version:

An unopinionated collection of useful functions and classes that I use widely in all my projects. 🔧

344 lines (323 loc) • 11.2 kB
import type { Callback } from "../types.js"; import type { FulfilledHandler, PromiseExecutor, RejectedHandler } from "./types.js"; /** * A wrapper class representing an enhanced version of the native {@link Promise} object. * * It provides additional properties to check the state of the promise itself. * The state can be either `pending`, `fulfilled` or `rejected` and is accessible through * the {@link SmartPromise.isPending}, {@link SmartPromise.isFulfilled} & {@link SmartPromise.isRejected} properties. * * --- * * @example * ```ts * const promise = new SmartPromise<string>((resolve, reject) => * { * setTimeout(() => resolve("Hello, World!"), 1_000); * }); * * console.log(promise.isPending); // true * console.log(promise.isFulfilled); // false * * console.log(await promise); // "Hello, World!" * * console.log(promise.isPending); // false * console.log(promise.isFulfilled); // true * ``` * * --- * * @template T The type of value the promise will eventually resolve to. Default is `void`. */ export default class SmartPromise<T = void> implements Promise<T> { /** * Wraps a new {@link SmartPromise} object around an existing native {@link Promise} object. * * --- * * @example * ```ts * const request = fetch("https://api.example.com/data"); * const smartRequest = SmartPromise.FromPromise(request); * * console.log(request.isPending); // Throws an error: `isPending` is not a property of `Promise`. * console.log(smartRequest.isPending); // true * * const response = await request; * console.log(smartRequest.isFulfilled); // true * ``` * * --- * * @param promise The promise to wrap. * * @returns A new {@link SmartPromise} object that wraps the provided promise. */ public static FromPromise<T>(promise: Promise<T>): SmartPromise<T> { return new SmartPromise((resolve, reject) => promise.then(resolve, reject)); } /** * A flag indicating whether the promise is still pending or not. * * The protected property is the only one that can be modified directly by the derived classes. * If you're looking for the public and readonly property, use the {@link SmartPromise.isPending} getter instead. */ protected _isPending: boolean; /** * A flag indicating whether the promise is still pending or not. */ public get isPending(): boolean { return this._isPending; } /** * A flag indicating whether the promise has been fulfilled or not. * * The protected property is the only one that can be modified directly by the derived classes. * If you're looking for the public and readonly property, use the {@link SmartPromise.isFulfilled} getter instead. */ protected _isFulfilled: boolean; /** * A flag indicating whether the promise has been fulfilled or not. */ public get isFulfilled(): boolean { return this._isFulfilled; } /** * A flag indicating whether the promise has been rejected or not. * * The protected property is the only one that can be modified directly by the derived classes. * If you're looking for the public and readonly property, use the {@link SmartPromise.isRejected} getter instead. */ protected _isRejected: boolean; /** * A flag indicating whether the promise has been rejected or not. */ public get isRejected(): boolean { return this._isRejected; } /** * The native {@link Promise} object wrapped by this instance. */ protected readonly _promise: Promise<T>; /** * Initializes a new instance of the {@link SmartPromise} class. * * --- * * @example * ```ts * const promise = new SmartPromise<string>((resolve, reject) => * { * setTimeout(() => resolve("Hello, World!"), 1_000); * }); * ``` * * --- * * @param executor * The function responsible for eventually resolving or rejecting the promise. * Similarly to the native {@link Promise} object, it's immediately executed after the promise is created. */ public constructor(executor: PromiseExecutor<T>) { this._isPending = true; this._isFulfilled = false; this._isRejected = false; const _onFulfilled = (result: T): T => { this._isPending = false; this._isFulfilled = true; return result; }; const _onRejected = (reason: unknown): never => { this._isPending = false; this._isRejected = true; throw reason; }; this._promise = new Promise<T>(executor) .then(_onFulfilled, _onRejected); } /** * Creates a new {@link Promise} identical to the one wrapped by this instance, with a different reference. * * --- * * @example * ```ts * const promise = new SmartPromise<string>((resolve, reject) => * { * setTimeout(() => resolve("Hello, World!"), 1_000); * }); * * console.log(await promise.then()); // "Hello, World!" * ``` * * --- * * @returns A new {@link Promise} identical to the original one. */ public then(onFulfilled?: null): Promise<T>; /** * Attaches a callback that executes right after the promise is fulfilled. * * The previous result of the promise is passed as the argument to the callback. * The callback's return value is considered the new promise's result instead. * * --- * * @example * ```ts * const promise = new SmartPromise<string>((resolve, reject) => * { * setTimeout(() => resolve("Hello, World!"), 1_000); * }); * * promise.then((result) => console.log(result)); // "Hello, World!" * ``` * * --- * * @template F The type of value the new promise will eventually resolve to. Default is `T`. * * @param onFulfilled The callback to execute once the promise is fulfilled. * * @returns A new {@link Promise} resolved with the return value of the callback. */ public then<F = T>(onFulfilled: FulfilledHandler<T, F>, onRejected?: null): Promise<F>; /** * Attaches callbacks that executes right after the promise is fulfilled or rejected. * * The previous result of the promise is passed as the argument to the fulfillment callback. * The fulfillment callback's return value is considered the new promise's result instead. * * If an error is thrown during execution, the rejection callback is then executed instead. * * Also note that: * - If the rejection callback runs properly, the error is considered handled. * The rejection callback's return value is considered the new promise's result. * - If the rejection callback throws an error, the new promise is rejected with that error. * * --- * * @example * ```ts * const promise = new SmartPromise((resolve, reject) => * { * setTimeout(resolve, Math.random() * 1_000); * setTimeout(reject, Math.random() * 1_000); * }); * * promise.then(() => console.log("OK!"), () => console.log("KO!")); // "OK!" or "KO!" * ``` * * --- * * @template F The type of value the new promise will eventually resolve to. Default is `T`. * @template R The type of value the new promise will eventually resolve to. Default is `never`. * * @param onFulfilled The callback to execute once the promise is fulfilled. * @param onRejected The callback to execute once the promise is rejected. * * @returns A new {@link Promise} resolved or rejected based on the callbacks. */ public then<F = T, R = never>( onFulfilled: FulfilledHandler<T, F>, onRejected: RejectedHandler<unknown, R> ): Promise<F | R>; public then<F = T, R = never>( onFulfilled?: FulfilledHandler<T, F> | null, onRejected?: RejectedHandler<unknown, R> | null ): Promise<F | R> { return this._promise.then(onFulfilled, onRejected); } /** * Creates a new {@link Promise} identical to the one wrapped by this instance, with a different reference. * * --- * * @example * ```ts * const promise = new SmartPromise((resolve, reject) => * { * setTimeout(() => reject(new Exception("An unknown error occurred.")), 1_000); * }); * * promise.catch(); // "Uncaught Exception: An unknown error occurred." * ``` * * --- * * @returns A new {@link Promise} identical to the original one. */ public catch(onRejected?: null): Promise<T>; /** * Attaches a callback to handle the potential rejection of the promise. * If it happens, the callback is then executed. * * Also note that: * - If the callback runs properly, the error is considered handled. * The callback's return value is considered the new promise's result. * - If the callback throws an error, the new promise is rejected with that error. * * --- * * @example * ```ts * const promise = new SmartPromise((resolve, reject) => * { * setTimeout(() => reject(new Exception("An unknown error occurred.")), 1_000); * }); * * promise.catch((reason) => console.error(reason)); // "Uncaught Exception: An unknown error occurred." * ``` * * --- * * @template R The type of value the new promise will eventually resolve to. Default is `T`. * * @param onRejected The callback to execute once the promise is rejected. * * @returns A new {@link Promise} able to catch and handle the potential error. */ public catch<R = never>(onRejected: RejectedHandler<unknown, R>): Promise<T | R>; public catch<R = never>(onRejected?: RejectedHandler<unknown, R> | null): Promise<T | R> { return this._promise.catch(onRejected); } /** * Attaches a callback that executes right after the promise is settled, regardless of the outcome. * * --- * * @example * ```ts * const promise = new SmartPromise((resolve, reject) => * { * setTimeout(resolve, Math.random() * 1_000); * setTimeout(reject, Math.random() * 1_000); * }); * * * promise * .then(() => console.log("OK!")) // Logs "OK!" if the promise is fulfilled. * .catch(() => console.log("KO!")) // Logs "KO!" if the promise is rejected. * .finally(() => console.log("Done!")); // Always logs "Done!". * ``` * * --- * * @param onFinally The callback to execute when once promise is settled. * * @returns A new {@link Promise} that executes the callback once the promise is settled. */ public finally(onFinally?: Callback | null): Promise<T> { return this._promise.finally(onFinally); } public readonly [Symbol.toStringTag]: string = "SmartPromise"; }