type UseResultPromiseInstead = "Returning a promise here is not advisable, use resultOrElse instead"; type NoMapables = T extends Mapable | MapablePromise ? UseResultPromiseInstead : T extends Promise ? Promise : Promise; type ResultOrElse = T extends Mapable | MapablePromise ? MapablePromise : never; interface Mapable { mapOrElse( even: (n: T) => U, odd: (n: T) => U, ): U; } interface MapablePromise { mapOrElse( even: (n: T) => U, odd: (n: T) => U, ): NoMapables; resultOrElse | MapablePromise>( even: (n: T) => U, odd: (n: T) => U, ): ResultOrElse; } class OddOrEven implements Mapable { constructor(private value: number) {} mapOrElse(even: (n: number) => U, odd: (n: number) => U): U { return this.value % 2 == 0 ? even(this.value) : odd(this.value); } } function promisedMapable(mapable: Mapable) { return new Promised(Promise.resolve(mapable)); } class Promised implements Promise>, MapablePromise { constructor(private promise: Promise>) {} mapOrElse(even: (n: T) => U, odd: (n: T) => U): NoMapables { const rv = this.promise.then( (mapable) => mapable.mapOrElse(even, odd), ); return rv as NoMapables; } resultOrElse | MapablePromise>( even: (n: T) => U, odd: (n: T) => U, ): ResultOrElse { const rv = this.promise.then((mapable) => mapable.mapOrElse(even, odd)); return new Promised( rv as unknown as Promise>, ) as MapablePromise as ResultOrElse; } then, TResult2 = never>( onfulfilled?: | ((value: Mapable) => TResult1 | PromiseLike) | null | undefined, onrejected?: | ((reason: unknown) => TResult2 | PromiseLike) | null | undefined, ): Promise { return this.promise.then(onfulfilled, onrejected); } catch( onrejected?: | ((reason: unknown) => TResult | PromiseLike) | null | undefined, ): Promise | TResult> { return this.promise.catch(onrejected); } finally( onfinally?: (() => void) | null | undefined, ): Promise> { return this.finally(onfinally); } get [Symbol.toStringTag](): string { return "Promised"; } } const _ = new OddOrEven(1) .mapOrElse( (n) => promisedMapable(new OddOrEven(n + 3)), (n) => promisedMapable(new OddOrEven(n + 5)), ) .mapOrElse>( (n) => new OddOrEven(n + 11), (n) => new OddOrEven(n + 13), ); new OddOrEven(1) .mapOrElse>( (n) => new OddOrEven(n + 3), (n) => new OddOrEven(n + 5), ) .mapOrElse( (n) => promisedMapable(new OddOrEven(n + 7)), (n) => promisedMapable(new OddOrEven(n + 9)), ).resultOrElse>( (n) => new OddOrEven(n + 11), (n) => new OddOrEven(n + 13), ).resultOrElse>( (n) => promisedMapable(new OddOrEven(n + 15)), (n) => promisedMapable(new OddOrEven(n + 17)), ).mapOrElse( (even) => console.log("Even:", even), (odd) => console.log("Odd: ", odd), ).finally(() => console.log("1 Finished")); new OddOrEven(2) .mapOrElse>( (n) => new OddOrEven(n + 3), (n) => new OddOrEven(n + 5), ) .mapOrElse( (n) => promisedMapable(new OddOrEven(n + 7)), (n) => promisedMapable(new OddOrEven(n + 9)), ).resultOrElse>( (n) => new OddOrEven(n + 11), (n) => new OddOrEven(n + 13), ).resultOrElse>( (n) => promisedMapable(new OddOrEven(n + 15)), (n) => promisedMapable(new OddOrEven(n + 17)), ).mapOrElse( (even) => console.log("Even:", even), (odd) => console.log("Odd: ", odd), ).finally(() => console.log("2 Finished"));