import { isPromiseLike } from "./isPromiseLike"; export interface SyncInspectablePromise extends Promise { status: PromiseStatus; value?: T; reason?: unknown; } export interface SyncInspectableDeferred { promise: SyncInspectablePromise; resolve: (value: T | PromiseLike) => void; reject: (reason: unknown) => void; } export const PromiseStatus = { PENDING: "pending", FULFILLED: "fulfilled", REJECTED: "rejected", } as const; export type PromiseStatus = (typeof PromiseStatus)[keyof typeof PromiseStatus]; export type PromiseState = | { status: typeof PromiseStatus.PENDING; } | { status: typeof PromiseStatus.FULFILLED; value: T; } | { status: typeof PromiseStatus.REJECTED; reason: unknown; }; export function inspect( promise: SyncInspectablePromise, ): PromiseState { if (promise.status === PromiseStatus.PENDING) { return { status: PromiseStatus.PENDING, }; } else if (promise.status === PromiseStatus.FULFILLED && "value" in promise) { return { status: PromiseStatus.FULFILLED, value: promise.value as T, }; } else if (promise.status === PromiseStatus.REJECTED && "reason" in promise) { return { status: PromiseStatus.REJECTED, reason: promise.reason, }; } else { throw new Error("Invalid promise state"); } } function makeSyncInspectable( promise: Promise, ): SyncInspectablePromise { if ( "status" in promise && Object.values(PromiseStatus).some((status) => status === promise.status) ) { return promise as SyncInspectablePromise; } const syncInspectablePromise: SyncInspectablePromise = Object.assign( promise, { status: PromiseStatus.PENDING }, ); syncInspectablePromise.then( (value) => { syncInspectablePromise.status = PromiseStatus.FULFILLED; syncInspectablePromise.value = value; }, (reason) => { syncInspectablePromise.status = PromiseStatus.REJECTED; syncInspectablePromise.reason = reason; }, ); return syncInspectablePromise; } export function resolve(value: T): SyncInspectablePromise> { if (isPromiseLike(value)) { return makeSyncInspectable( value instanceof Promise ? value : Promise.resolve(value), ); } return Object.assign(Promise.resolve(value), { status: PromiseStatus.FULFILLED, value, }) as SyncInspectablePromise>; } export function reject(error: unknown): SyncInspectablePromise { return Object.assign(Promise.reject(error), { status: PromiseStatus.REJECTED, reason: error, }) as SyncInspectablePromise; } export function defer(): SyncInspectableDeferred { let resolvePromise!: (value: T | PromiseLike) => void; let rejectPromise!: (reason: unknown) => void; let isSettled = false; const promise = Object.assign( new Promise((resolvePromise_, rejectPromise_) => { resolvePromise = resolvePromise_; rejectPromise = rejectPromise_; }), { status: PromiseStatus.PENDING }, ) as SyncInspectablePromise; const fulfill = (value: T) => { if (promise.status !== PromiseStatus.PENDING) { return; } promise.status = PromiseStatus.FULFILLED; promise.value = value; resolvePromise(value); }; const rejectPromiseState = (reason: unknown) => { if (promise.status !== PromiseStatus.PENDING) { return; } promise.status = PromiseStatus.REJECTED; promise.reason = reason; rejectPromise(reason); }; const reject = (reason: unknown) => { if (isSettled) { return; } isSettled = true; rejectPromiseState(reason); }; return { promise, resolve(value) { if (isSettled) { return; } isSettled = true; try { const source = resolve(value) as SyncInspectablePromise; if (source === promise) { rejectPromiseState( new TypeError("A promise cannot be resolved with itself"), ); return; } const state = inspect(source); if (state.status === PromiseStatus.FULFILLED) { fulfill(state.value); } else if (state.status === PromiseStatus.REJECTED) { rejectPromiseState(state.reason); } else { source.then(fulfill, rejectPromiseState); } } catch (error) { rejectPromiseState(error); } }, reject, }; }