Consider the following example. fetchItems function returns response or response body depending on passed onlyBody argument which defaults to
The main problem you're facing is that TypeScript does not support partial type parameter inference. Either you must manually specify all type parameters (except for ones with defaults), or you let the compiler infer all type parameters, but you cannot specify some and let the compiler infer the rest.
Using overloads instead of generic type parameters, as shown in @Nenad's answer is one way around this for types like boolean with a small number of possible values. The issue mentioned in the comments with a boolean parameter (instead of a true or false one) can be solved by adding another overload, like this:
function fetchItems(
url: string,
onlyBody: false
): Promise>;
function fetchItems(url: string, onlyBody?: true): Promise;
// add this overload
function fetchItems(
url: string,
onlyBody: boolean
): Promise>;
function fetchItems(url: string, onlyBody: boolean = true) {
return Promise.resolve({ body: "some data" } as any).then(
res => (onlyBody ? res.body : res)
);
}
const a = fetchItems("url", false); // Promise>
const b = fetchItems("url", true); // Promise
const c = fetchItems("url"); // Promise
const d = fetchItems("url", Math.random() < 0.5);
// Promise>
I know of two other workarounds, which I've been calling Currying and Dummying:
The "Currying" workaround splits your single generic function of two type parameters into two curried functions of one type parameter each. One you specify, the other you infer. Like this:
const fetchItems = () => (
url: string,
onlyBody: B = true as B
) => {
return Promise.resolve({ body: "some data" } as any).then<
B extends true ? T : HttpResponse
>(res => (onlyBody ? res.body : res));
};
And you call it like this:
const a = fetchItems()("url", false); // Promise>
const b = fetchItems()("url", true); // Promise
const c = fetchItems()("url"); // Promise
const d = fetchItems()("url", Math.random() < 0.5);
// Promise>
Or, since all of those use fetchItems, you can save that to its own function and use it, for a bit less redundancy:
const fetchItemsString = fetchItems();
const e = fetchItemsString("url", false); // Promise>
const f = fetchItemsString("url", true); // Promise
const g = fetchItemsString("url"); // Promise
const h = fetchItemsString("url", Math.random() < 0.5);
// Promise>
The "Dummying" workaround lets the compiler infer all the parameter types, even the ones you want to specify manually. It does this by having the function take dummy parameters of the types you would normally specify manually; the function ignores the dummy parameters:
function fetchItems(
dummyT: T,
url: string,
onlyBody: B = true as B
) {
return Promise.resolve({ body: "some data" } as any).then<
B extends true ? T : HttpResponse
>(res => (onlyBody ? res.body : res));
}
const a = fetchItems("dummy", "url", false); // Promise>
const b = fetchItems("dummy", "url", true); // Promise
const c = fetchItems("dummy", "url"); // Promise
const d = fetchItems("dummy", "url", Math.random() < 0.5);
// Promise>
Since the dummy value is only for the benefit of the compiler and is unused at runtime, you can also use a type assertion to pretend you have an instance of the type instead of going through any trouble to create one:
const dummy = null! as string; // null at runtime, string at compile time
const e = fetchItems(dummy, "url", false); // Promise>
const f = fetchItems(dummy, "url", true); // Promise
const g = fetchItems(dummy, "url"); // Promise
const h = fetchItems(dummy, "url", Math.random() < 0.5);
// Promise>
Of course it's pretty easy to get a string value, so there's not much point using null! as string instead of "randomString", but for more complicated types it becomes more convenient to use the type assertion instead of trying to create a real instance that you'll just be throwing away.
Anyway, hope one of those works for you. Good luck!
Link to code