(Full code in TypeScript playground: link.)
I have a Result<T, E> type for computations that either succeed with a value of type T or fail with an error of type E.
type Result<T, E> =
| {ok: true, value: T}
| {ok: false, value: E};
I want to write a helper function that can run a list of Result-returning functions in sequence. If any function fails, stop processing and return the error. If all succeed, return a list of the success values.
Here's how you can implement that function specifically for lists of length three:
function sequenceFixed<T1, T2, T3, E>(fns: [() => Result<T1, E>, () => Result<T2, E>, () => Result<T3, E>]): Result<[T1, T2, T3], E> {
const [f1, f2, f3] = fns;
const r1 = f1();
if (!r1.ok) return r1;
const r2 = f2();
if (!r2.ok) return r2;
const r3 = f3();
if (!r3.ok) return r3;
return {ok: true, value: [r1.value, r2.value, r3.value]};
}
I want to use TypeScript 4.0's variadic tuple types to make this work for any length list. (The function body itself doesn't need to type check, just the call sites.)
Here are my two attempts:
declare function sequenceGeneric1<T extends Array<unknown>, E>(
fns: Readonly<{[P in keyof T]: () => Result<T[P], E>}>,
): Result<[...T], E>;
declare function sequenceGeneric2<Fns extends Array<unknown>, E>(
fns: Fns,
): Result<{[P in keyof Fns]: ExtractResultSuccessType<Fns[P]>}, E>;
type ExtractResultSuccessType<Fn> = Fn extends () => Result<infer T, unknown> ? T : unknown;
Calling those functions type-checks correctly if I explicitly pass type arguments, but I can't figure out how to get TypeScript to infer the type arguments.
declare function f1(): Result<string, string>;
declare function f2(): Result<boolean, string>;
declare function f3(): Result<number, string>;
function checkFixed() {
return sequenceFixed([f1, f2, f3]);
}
function checkGeneric1() {
return sequenceGeneric1([f1, f2, f3]);
}
function checkGeneric2() {
return sequenceGeneric2([f1, f2, f3]);
}
function check() {
// ok
const c1: Result<[string, boolean, number], string> = checkFixed();
// ERROR: Type 'Result<(string | number | boolean)[], unknown>' is not assignable to
// type 'Result<[string, boolean, number], string>'.
const c2: Result<[string, boolean, number], string> = checkGeneric1();
// ERROR: Type 'Result<(string | number | boolean)[], unknown>' is not assignable to
// type 'Result<[string, boolean, number], string>'.
const c3: Result<[string, boolean, number], string> = checkGeneric2();
}
function checkGeneric1WithAnnontation(): Result<[string, boolean, number], string> {
return sequenceGeneric1<[string, boolean, number], string>([f1, f2, f3]);
}