Skip to content

Instantly share code, notes, and snippets.

@SimonMeskens
Created April 6, 2022 14:47
Show Gist options
  • Save SimonMeskens/746e011500b2ffe4058299bf222b42b0 to your computer and use it in GitHub Desktop.
Save SimonMeskens/746e011500b2ffe4058299bf222b42b0 to your computer and use it in GitHub Desktop.
A lazy promise that only tries to resolve itself when prompted.
/**
* @module
* @copyright Simon Meskens 2022
* @license
* Copying and distribution of this file, with or without modification,
* are permitted in any medium without royalty, provided the copyright
* notice and this notice are preserved. This file is offered as-is,
* without any warranty.
*/
/**
* @template R, [T=never]
* @callback OnResult
* @param {T} [value]
* @returns {R | PromiseLike<R>}
*/
/**
* @template [T=*]
* @callback Finish
* @param {T | PromiseLike<T>} value
* @returns {void}
*/
/**
* Represents a lazy promise that only tries to resolve itself when accessed.
* @template T
* @extends Promise<T>
*/
export class Voucher extends Promise {
static [Symbol.species]() {
return Promise;
}
[Symbol.toStringTag]() {
return "Voucher";
}
/**
* The evaluator function.
* @type {OnResult<T>}
*/
#evaluate;
/**
* Resolves the Voucher.
* @type {Finish<T>}
*/
#resolve;
/**
* Rejects the Voucher.
* @type {Finish}
*/
#reject;
/**
* Has the Voucher been redeemed yet?
* @type {boolean}
*/
#done = false;
/**
* Constructs a new {@link Voucher}.
* @constructor
* @param {OnResult<T>} evaluator
* The evaluation function that runs on resolve.
*/
constructor(evaluator) {
/** @type {{ resolve: Finish<T>, reject: Finish }} */
let args;
super((resolve, reject) => {
args = { resolve, reject };
});
this.#resolve = args.resolve;
this.#reject = args.reject;
this.#evaluate = evaluator;
}
/**
* Evaluates the evaluator and returns the generated promise.
* @async
* @returns {Promise<T>}
* The generated promise.
*/
async redeem() {
if (this.#done) throw new Error("Can't execute Lazy twice.");
return Promise.resolve(this.#evaluate()).then(this.#resolve, this.#reject);
}
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @override
* @template [R1=T], [R2=never]
* @param {OnResult<R1, T>} [onfulfilled]
* The callback to execute when the Promise is resolved.
* @param {OnResult<R2, any>} [onrejected]
* The callback to execute when the Promise is rejected.
* @returns {Promise<R1 | R2>}
* A Promise for the completion of which ever callback is executed.
*/
then(onfulfilled, onrejected) {
if (!this.#done) this.redeem();
return super.then(onfulfilled, onrejected);
}
}
/**
* Creates a new {@link Voucher}.
* @template T
* @param {OnResult<T>} evaluator
* The evaluation function that runs on resolve.
* @returns {Voucher<T>}
* A voucher of the return type of the evaluator.
*/
export const voucher = evaluator =>
Promise.resolve({
then: (...args) => Promise.resolve(evaluator()).then(...args)
});
export default Voucher;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment