@byloth/core
Version:
An unopinionated collection of useful functions and classes that I use widely in all my projects. 🔧
132 lines (118 loc) • 4.8 kB
text/typescript
import type { PromiseResolver, PromiseRejecter, FulfilledHandler, RejectedHandler } from "./types.js";
import SmartPromise from "./smart-promise.js";
/**
* A class representing a {@link SmartPromise} that can be resolved or rejected from the "outside".
* The `resolve` and `reject` methods are exposed to allow the promise to be settled from another context.
*
* It's particularly useful in scenarios where the promise is created and needs to be awaited in one place,
* while being resolved or rejected in another (e.g. an event handler for an user interaction).
*
* This is a change in the approach to promises: instead of defining how the promise will be resolved (or rejected),
* you define how to handle the resolution (or rejection) when it occurs.
*
* ---
*
* @example
* ```ts
* const promise = new DeferredPromise<string, string[]>((value: string) => value.split(" "));
*
* promise.then((result) => console.log(result)); // ["Hello,", "World!"]
* promise.resolve("Hello, World!");
* ```
*
* ---
*
* @template T The type of value the promise expects to initially be resolved with. Default is `void`.
* @template F
* The type of value returned by the `onFulfilled` callback.
* This will be the actual type of value the promise will eventually resolve to. Default is `T`.
* @template R
* The type of value possibly returned by the `onRejected` callback.
* This will be coupled with the type of value the promise will eventually resolve to, if provided. Default is `never`.
*/
export default class DeferredPromise<T = void, F = T, R = never> extends SmartPromise<F | R>
{
/**
* The exposed function that allows to resolve the promise.
*
* This 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 DeferredPromise.resolve} getter instead.
*/
protected readonly _resolve: PromiseResolver<T>;
/**
* The exposed function that allows to reject the promise.
*/
public get resolve(): PromiseResolver<T> { return this._resolve; }
/**
* The exposed function that allows to reject the promise.
*
* This 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 DeferredPromise.reject} getter instead.
*/
protected readonly _reject: PromiseRejecter;
/**
* The exposed function that allows to reject the promise.
*/
public get reject(): PromiseRejecter { return this._reject; }
/**
* The native {@link Promise} object wrapped by this instance.
*/
declare protected readonly _promise: Promise<F | R>;
/**
* Initializes a new instance of the {@link DeferredPromise} class.
*
* ---
*
* @example
* ```ts
* const promise = new DeferredPromise<string, string[]>((value: string) => value.split(" "));
* ```
*
* ---
*
* @param onFulfilled The callback to execute once the promise is fulfilled.
* @param onRejected The callback to execute once the promise is rejected.
*/
public constructor(onFulfilled?: FulfilledHandler<T, F> | null, onRejected?: RejectedHandler<unknown, R> | null)
{
let _resolve: PromiseResolver<T>;
let _reject: PromiseRejecter;
super((resolve, reject) =>
{
// ReferenceError: Must call super constructor in derived class before accessing
// 'this' or returning from derived constructor.
//
_resolve = resolve as PromiseResolver<T>;
_reject = reject;
});
this._promise = this._promise.then(onFulfilled as FulfilledHandler<F | R>, onRejected);
this._resolve = _resolve!;
this._reject = _reject!;
}
/**
* Watches another promise and resolves or rejects this promise when the other one is settled.
*
* ---
*
* @example
* ```ts
* const promise = new Promise<string>((resolve) => setTimeout(() => resolve("Hello, World!"), 1_000));
* const deferred = new DeferredPromise<string, string[]>((value: string) => value.split(" "));
*
* deferred.then((result) => console.log(result)); // ["Hello,", "World!"]
* deferred.watch(promise);
* ```
*
* ---
*
* @param otherPromise The promise to watch.
*
* @returns The current instance of the {@link DeferredPromise} class.
*/
public watch(otherPromise: PromiseLike<T>): this
{
otherPromise.then(this.resolve, this.reject);
return this;
}
public override readonly [Symbol.toStringTag]: string = "DeferredPromise";
}