export type AnyError = any; export const NOT_SET = Symbol('NOT_SET'); export type NotSet = typeof NOT_SET; export type Result = | { readonly kind: 'Ok'; readonly value: T; } | { readonly kind: 'Err'; readonly error: E; }; /** * Invariant: * Before the promise is resolved, value becomes non-null. */ export type PromiseWrapper = { readonly promise: Promise>; result: Result, E> | NotSet; }; export interface PromiseWrapperOk extends PromiseWrapper { result: { readonly kind: 'Ok'; readonly value: Exclude; }; } export function wrapPromise( promise: Promise>, ): PromiseWrapper { // TODO confirm suspense works if the promise is already resolved. const wrapper: PromiseWrapper = { promise, result: NOT_SET }; promise .then((v) => { wrapper.result = { kind: 'Ok', value: v }; }) .catch((e) => { wrapper.result = { kind: 'Err', error: e }; }); return wrapper; } export function wrapResolvedValue( value: Exclude, ): PromiseWrapperOk { return { promise: Promise.resolve(value), result: { kind: 'Ok', value, }, }; } export function readPromise(p: PromiseWrapper): T { const { result } = p; if (result !== NOT_SET) { const resultKind = result; if (resultKind.kind === 'Ok') { return resultKind.value; } else { throw resultKind.error; } } throw p.promise; } export type PromiseState = | { readonly kind: 'Pending'; readonly promise: Promise; } | Result; export function getPromiseState( p: PromiseWrapper, ): PromiseState { if (p.result !== NOT_SET) { return p.result; } return { kind: 'Pending', promise: p.promise, }; }