export class PinkyPromise<T> { // TODO add implementation }
export class PinkyPromise<T> { // TODO add implementation }
/** * A [Promise A+](https://promisesaplus.com/) spec compliant implementation written in typescript. */ export class PinkyPromise<T> implements PromiseLike<T> { #state: PromiseState<T> = { status: "pending" }; #fulfilledListeners: ((value: unknown) => unknown)[] = []; #rejectedListeners: ((reason: unknown) => unknown)[] = []; constructor(executor: PinkyPromiseExecutor<T>) { if (typeof executor !== "function") { throw new TypeError(`${executor} is not a function`); } try { executor(this.#resolve, this.#reject); } catch (executorError) { this.#reject(executorError); } } /** * Notifies the subscribed listeners if the promise is settled. */ #notify() { // Continue... }
/** * A [Promise A+](https://promisesaplus.com/) spec compliant implementation written in typescript. */ export class PinkyPromise<T> implements PromiseLike<T> { #state: PromiseState<T> = { status: "pending" }; #fulfilledListeners: ((value: unknown) => unknown)[] = []; #rejectedListeners: ((reason: unknown) => unknown)[] = []; constructor(executor: PinkyPromiseExecutor<T>) { if (typeof executor !== "function") { throw new TypeError(`${executor} is not a function`); } try { executor(this.#resolve, this.#reject); } catch (executorError) { this.#reject(executorError); } } /** * Notifies the subscribed listeners if the promise is settled. */ #notify() { // Continue... }
A promise is an object that represents the eventual result of an asynchronous operation.
Allows the subscription of listeners to the result of an asynchronous action or the reason why it has failed.
/** * A Promise is in one of these states: * * - pending: initial state, neither fulfilled nor rejected. * - fulfilled: meaning that the operation was completed successfully. * - rejected: meaning that the operation failed. */ type PromiseState<T> = | { readonly status: "pending" } | { readonly status: "fulfilled"; readonly value: T } | { readonly status: "rejected"; readonly reason: any }; export class PinkyPromise<T> { #state: PromiseState<T> = { status: "pending" }; }
/** * A Promise is in one of these states: * * - pending: initial state, neither fulfilled nor rejected. * - fulfilled: meaning that the operation was completed successfully. * - rejected: meaning that the operation failed. */ type PromiseState<T> = | { readonly status: "pending" } | { readonly status: "fulfilled"; readonly value: T } | { readonly status: "rejected"; readonly reason: any }; export class PinkyPromise<T> { #state: PromiseState<T> = { status: "pending" }; }
export class PinkyPromise<T> { #state: PromiseState<T> = { status: "pending" }; /** * Settles promise, transitioning it from state pending to either `fulfilled` or `rejected`. */ static #transition( pinkyPromise: PinkyPromise<any>, state: PromiseState<any>, ) { if (pinkyPromise.#state.status === "pending") { pinkyPromise.#state = state; } } }
export class PinkyPromise<T> { #state: PromiseState<T> = { status: "pending" }; /** * Settles promise, transitioning it from state pending to either `fulfilled` or `rejected`. */ static #transition( pinkyPromise: PinkyPromise<any>, state: PromiseState<any>, ) { if (pinkyPromise.#state.status === "pending") { pinkyPromise.#state = state; } } }
export class PinkyPromise<T> { #state: PromiseState<T> = { status: "pending" }; constructor(executor: PinkyPromiseExecutor<T>) { if (typeof executor !== "function") { throw new TypeError(`${executor} is not a function`); } try { executor(this.#resolve, this.#reject); } catch (executorError) { this.#reject(executorError); } } #transition(pinkyPromise: PinkyPromise<any>, state: PromiseState<any>) { /* implementation */ } #resolve = (value: T | PromiseLike<T>): void => { PinkyPromise.#transition(this, { status: "fulfilled", value }); }; #reject = (reason: unknown): void => { PinkyPromise.#transition(this, { status: "rejected", reason }); }; }
export class PinkyPromise<T> { #state: PromiseState<T> = { status: "pending" }; constructor(executor: PinkyPromiseExecutor<T>) { if (typeof executor !== "function") { throw new TypeError(`${executor} is not a function`); } try { executor(this.#resolve, this.#reject); } catch (executorError) { this.#reject(executorError); } } #transition(pinkyPromise: PinkyPromise<any>, state: PromiseState<any>) { /* implementation */ } #resolve = (value: T | PromiseLike<T>): void => { PinkyPromise.#transition(this, { status: "fulfilled", value }); }; #reject = (reason: unknown): void => { PinkyPromise.#transition(this, { status: "rejected", reason }); }; }
onFulfill(value)
or onRejection(reason)
once.new Promise(resolve => resolve(5)) .then(a => a + 5) .then(a => a + 6) .then(console.log) // Logs 16 new Promise((_, reject) => reject(5)) .then(a => a + 5) .then(a => a + 6) .catch(console.log) // Logs 5
new Promise(resolve => resolve(5)) .then(a => a + 5) .then(a => a + 6) .then(console.log) // Logs 16 new Promise((_, reject) => reject(5)) .then(a => a + 5) .then(a => a + 6) .catch(console.log) // Logs 5
returns a new Promise that contains the output of computation of either onFulfill
or onRejection
.
export class PinkyPromise<T> implements PromiseLike<T> { #notify() { /* TO DO add implemetation */ } static #transition( pinkyPromise: PinkyPromise<any>, state: PromiseState<any>, ) { if (pinkyPromise.#state.status === "pending") { pinkyPromise.#state = state; pinkyPromise.#notify(); } } then<TResult1 = T, TResult2 = never>( onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined, ): PinkyPromise<TResult1 | TResult2> { return new PinkyPromise<TResult1 | TResult2>((resolve, reject) => { // do something then resolve this.#notify(); }); } }
export class PinkyPromise<T> implements PromiseLike<T> { #notify() { /* TO DO add implemetation */ } static #transition( pinkyPromise: PinkyPromise<any>, state: PromiseState<any>, ) { if (pinkyPromise.#state.status === "pending") { pinkyPromise.#state = state; pinkyPromise.#notify(); } } then<TResult1 = T, TResult2 = never>( onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined, ): PinkyPromise<TResult1 | TResult2> { return new PinkyPromise<TResult1 | TResult2>((resolve, reject) => { // do something then resolve this.#notify(); }); } }
export class PinkyPromise<T> implements PromiseLike<T> { #state: PromiseState<T> = { status: "pending" }; #fulfilledListeners: ((value: unknown) => unknown)[] = []; #rejectedListeners: ((reason: unknown) => unknown)[] = []; // Notify listeners if the Promise is settled #notify() { switch (this.#state.status) { case "pending": break; case "fulfilled": { this.#fulfilledListeners.forEach((cb) => queueMicrotask(() => cb(this.#state.value))); break; } case "rejected": { this.#rejectedListeners.forEach((cb) => queueMicrotask(() => cb(this.#state.reason))); break; } } // remove listeners at the end if the Promise is not settled if (this.#state.status !== "pending") { this.#fulfilledListeners.splice(0, this.#fulfilledListeners.length); this.#rejectedListeners.splice(0, this.#rejectedListeners.length); } } }
export class PinkyPromise<T> implements PromiseLike<T> { #state: PromiseState<T> = { status: "pending" }; #fulfilledListeners: ((value: unknown) => unknown)[] = []; #rejectedListeners: ((reason: unknown) => unknown)[] = []; // Notify listeners if the Promise is settled #notify() { switch (this.#state.status) { case "pending": break; case "fulfilled": { this.#fulfilledListeners.forEach((cb) => queueMicrotask(() => cb(this.#state.value))); break; } case "rejected": { this.#rejectedListeners.forEach((cb) => queueMicrotask(() => cb(this.#state.reason))); break; } } // remove listeners at the end if the Promise is not settled if (this.#state.status !== "pending") { this.#fulfilledListeners.splice(0, this.#fulfilledListeners.length); this.#rejectedListeners.splice(0, this.#rejectedListeners.length); } } }
Promise.resolve( new Promise(resolve => Promise.resolve(5)) ).then(console.log)
Promise.resolve( new Promise(resolve => Promise.resolve(5)) ).then(console.log)
Promise.resolve( new Promise((_ ,reject) => reject(10)) ).then( a => Promise.resolve(a + 10), a => Promise.resolve(a + 2) ) .then(console.log)
Promise.resolve( new Promise((_ ,reject) => reject(10)) ).then( a => Promise.resolve(a + 10), a => Promise.resolve(a + 2) ) .then(console.log)
Promise.resolve( new Promise(resolve => Promise.resolve(5)) ).then(console.log) // Logs 5
Promise.resolve( new Promise(resolve => Promise.resolve(5)) ).then(console.log) // Logs 5
Promise.resolve( new Promise((_ ,reject) => reject(10)) ).then( a => Promise.resolve(a + 10), a => Promise.resolve(a + 2) ) .then(console.log) // Logs 12
Promise.resolve( new Promise((_ ,reject) => reject(10)) ).then( a => Promise.resolve(a + 10), a => Promise.resolve(a + 2) ) .then(console.log) // Logs 12
The output of .then()
callbacks and the argument passed to resolve
and reject
is always unwrapped,
i.e not a Promise.
The algorithm has to conform to the
Promise A+ Promise resolution procedure.
Our Implementation passes all tests of the Promise A+ Compliance test suite π.
pnpm test:a-plus # Several skipped lines # [...] The value is `true` with `Boolean.prototype` modified to have a `then` method β already-fulfilled β immediately-fulfilled β eventually-fulfilled β already-rejected β immediately-rejected β eventually-rejected The value is `1` with `Number.prototype` modified to have a `then` method β already-fulfilled β immediately-fulfilled β eventually-fulfilled β already-rejected β immediately-rejected β eventually-rejected 872 passing (13s)
pnpm test:a-plus # Several skipped lines # [...] The value is `true` with `Boolean.prototype` modified to have a `then` method β already-fulfilled β immediately-fulfilled β eventually-fulfilled β already-rejected β immediately-rejected β eventually-rejected The value is `1` with `Number.prototype` modified to have a `then` method β already-fulfilled β immediately-fulfilled β eventually-fulfilled β already-rejected β immediately-rejected β eventually-rejected 872 passing (13s)
https://github.com/FaberVitale/pinky-promise/blob/main/lib/pinky-promise.ts