I'd like to wrap a given generic function (that is not under my control) with my own generic function.
e.g.
type givenFunction= <T, V>(a: string, b: V) => Promise<T>;
// This is what I want, though I'd like to infer all of the types from `givenFunction`
type myFunction = <T, V>(b: V) => Promise<T>
Also, I'd like to reuse most of the given functions' type definitions (using Parameters
and ReturnType
). It kinda works already, only that it's not generic anymore.
Here's the real-world example of what I'm trying to do: TypeScript Playground
CodePudding user response:
You can't do this purely at the type level. If you are okay with some runtime effects, you can make a higher order function that converts one generic function into another. For example:
function passInitialString<A extends any[], R>(t: string, f: (t: string, ...r: A) => R) {
return (...r: A) => f(t, ...r);
}
This function will take any function f
whose first argument is a string, and produce another function which does not need that argument. It might not seem like anything special will happen here, but if f
happens to be generic, then so will the returned function.
Let's use the request
function from graphql-request in your playground link as an example:
import { request as gqlRequest } from 'graphql-request';
declare const endpoint: string;
const request = passInitialString(endpoint, gqlRequest);
/*
const request: <T = any, V = Variables>(
document: RequestDocument,
variables?: V | undefined,
requestHeaders?: HeadersInit | undefined
) => Promise<T>
*/
The request
type is generic and has exactly the type you want.
If you don't want any runtime effects, there's not much you can do except to write out the desired generic type manually.
TypeScript doesn't have direct support for higher kinded types of the sort requested in microsoft/TypeScript#1213, nor does it have generic values of the sort requested in microsoft/TypeScript#17574, so there's no way to express the kind of type manipulation you are performing in the type system.
Writing out types manually is annoying, and it doesn't automatically update with the original function type, and you might need to declare a lot of other types:
export type Variables = { [key: string]: any }
export type RequestDocument = string | DocumentNode
type Request = <T = any, V = Variables>(
document: RequestDocument,
variables?: V | undefined,
requestHeaders?: HeadersInit | undefined
) => Promise<T>
So hopefully you're okay with a solution like passInitialString()
.