Currently working with TypeScript version 5.4.5, I've developed the following utility helpers:
type Result<T, E> = [T, null] | [null, E];
function ok<T>(good: T): Result<T, null> {
return [good, null];
}
function err<E>(bad: E): Result<null, E> {
return [null, bad];
}
I have a utility function that utilizes the Result
type for fetch responses which compiles without any TypeScript errors:
type FetchResult = Result<Response, Error>;
async function tryFetch(fetch: () => Promise<Response>): Promise<FetchResult> {
try {
const result = await fetch();
if (!result.ok) {
return [null, new Error(`Failed to fetch: ${result.statusText}`)];
}
return [result, null];
} catch (error) {
return [null, error as Error];
}
}
Now, presenting another helper function that aims to follow the same type system but encounters a TypeScript error:
type FetchJsonResult<T> = Result<T, Error>;
async function tryFetchJson<T>(fetchFn: () => Promise<Response>): Promise<FetchJsonResult<T>> {
try {
const response = await fetchFn();
if (!response.ok) {
return err(new Error(`Failed to fetch: ${response.statusText}`));
}
const data: T = await response.json();
return ok(data);
} catch (error) {
return err(error as Error);
}
}
The encountered errors are:
Type 'Result<null, Error>' is not assignable to type 'FetchJsonResult<T>'.
Type '[null, null]' is not assignable to type 'FetchJsonResult<T>'.
Type '[null, null]' is not assignable to type '[T, null]'.
Type at position 0 in source is not compatible with type at position 0 in target.
Type 'null' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type that may not relate to 'null'.
and
Type 'Result<T, null>' is not assignable to type 'FetchJsonResult<T>'.
Type '[null, null]' is not assignable to type 'FetchJsonResult<T>'.
and
Type 'Result<null, Error>' is not assignable to type 'FetchJsonResult<T>'.
Is there a more efficient way to achieve the above using a single implementation of ok
and err
without requiring a cast to FetchJsonResult?