Skip to content

Instantly share code, notes, and snippets.

@danfma
Last active August 21, 2019 17:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danfma/9e6ca5903ebe0d6e77dd62395ecd9050 to your computer and use it in GitHub Desktop.
Save danfma/9e6ca5903ebe0d6e77dd62395ecd9050 to your computer and use it in GitHub Desktop.
Problem with TypeScript conditional types and generics.
type Task<T> = () => T | Promise<T>;
type Mapper<T, R> = (instance: LoadingInstance<T>) => R;
interface LoadingInstance<T> {
result: T | undefined;
loading: boolean;
loadingError: Error | null;
reload: () => void;
}
/* FIRST SCENARIO - USING OVERLOADS */
// first overload - no mapper
function useLoading<T>(
task: Task<T>,
dependencies: any[],
loadingDelay?: number
): LoadingInstance<T>;
// second overload - with mapper
function useLoading<T, R>(
task: Task<T>,
dependencies: any[],
loadingDelay: number | undefined,
mapper: Mapper<T, R>
): R;
// implementation
function useLoading<T, R>(
task: Task<T>,
dependencies: any[] = [],
loadingDelay = 500,
mapper?: Mapper<T, R>
): LoadingInstance<T> | R {
throw new Error('NOT IMPLEMENTED');
}
/* Preparing some data for testing the typings */
const doTask = () => ({ listen: 'song' });
type DoTaskResult = ReturnType<typeof doTask>;
function extractListen(instance: LoadingInstance<DoTaskResult>) {
const { result } = instance;
return result && result.listen;
}
/* First test - OK */
// usage 1 - expected result: LoadingInstance<DoTaskResult> (OK)
const result1 = useLoading(doTask, []);
// usage 2 - expected result: string | undefined (OK)
const listen1 = useLoading(doTask, [], undefined, extractListen);
/* SECOND SCENARIO - USING CONDITIONAL TYPES */
type ExtractLoadingInstance<T, TMapper> =
TMapper extends Mapper<T, infer R> ? R
: LoadingInstance<T>;
function useLoading2<T, TMapper extends Mapper<T, any>>(
task: Task<T>,
dependencies: any[] = [],
loadingDelay = 500,
mapper?: TMapper
): ExtractLoadingInstance<T, TMapper> {
throw new Error('NOT IMPLEMENTED');
}
// usage 1 - expected result: LoadingInstance<DoTaskResult> (FAIL - result is any)
const result2 = useLoading2(doTask, []);
// usage 2 - expected result: string | undefined (OK)
const listen2 = useLoading2(doTask, [], undefined, extractListen);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment