TypeScript Infer in Depth
One conditional check can carry several infer declarations in different positions. Each position binds its own variable from the same match.
Infer in several positions at once
CallShape reads both parameters and the return type in one check. The result object carries one field per bound variable.
type CallShape<F> = F extends (first: infer A, second: infer B) => infer R ? { first: A; second: B; result: R } : never;
function combine(count: number, labelText: string): boolean {
return count > 0 && labelText.length > 0;
}
type Shape = CallShape<typeof combine>;
const shape: Shape = { first: 2, second: 'ready', result: true };
console.log(shape.first);
console.log(shape.second);
console.log(shape.result);Infer unwraps a promise
UnwrapPromise places infer inside a Promise pattern and returns the carried type. The built-in Awaited utility performs this same unwrap, and the home-grown version shows the machinery.
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Score = UnwrapPromise<Promise<number>>;
type Plain = UnwrapPromise<string>;
const score: Score = 42;
const plain: Plain = 'done';
console.log(score);
console.log(plain);Infer with a constraint
The constraint after U requires the element type to extend string. A number array fails that requirement and takes the false branch.
type StringElement<T> = T extends (infer U extends string)[] ? U : 'not text';
type Word = StringElement<string[]>;
type Num = StringElement<number[]>;
const word: Word = 'hello';
const num: Num = 'not text';
console.log(word);
console.log(num);Example
type AsyncCall<F> = F extends (input: infer I) => Promise<infer O> ? { input: I; output: O } : never;
function fetchUser(id: number): Promise<string> {
return Promise.resolve('user-' + id);
}
type FetchShape = AsyncCall<typeof fetchUser>;
const entry: FetchShape = { input: 5, output: 'user-5' };
console.log(entry.input);
console.log(entry.output);Key points
- Infer positions in one check bind together, so one match can read a whole function shape.
- A promise pattern with infer unwraps the value type that a promise carries.
- An infer variable can carry a constraint, written as infer U extends string.
- The constraint limits the matches that bind the variable, and other inputs take the false branch.
- A position that only produces values is called covariant, and infer in such a position can collect a union of candidates.
In this example, one check reads the input of a promise-returning function and the value inside its promise, then pairs both types in one result object.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท TypeScript Playground
Exercise
What does UnwrapPromise<Promise<number>> produce?