Aron Griffis
Aron Griffis

Reputation: 1729

Can I pass a generic type as a type parameter?

I have an API client that can be sync or async, depending on the fetcher. Dramatically simplified, it's like this:

function makeClient<F>({fetcher}: any): any {
  return {
    query: () => fetcher(`query`) as F<QueryResult>,
    toc: () => fetcher(`toc`) as F<TocResult>,
  }
}

makeClient<Promise>({fetcher: window.fetch}) // returns promises
makeClient<Identity>({fetcher: fetchSync}) // returns data

I'm running into Type 'F' is not generic. Is there a way to do this?

Here is the playground: https://tsplay.dev/N54OMw

Upvotes: 3

Views: 3075

Answers (1)

sam256
sam256

Reputation: 1421

Type parameters stand in for composed types, so as far as I know they can't be generic in the way it looks like you want.

You can, however, make your return type depend on a type argument using conditional types. Here's a very explicit way to do it:

interface QueryResult {}
interface TocResult {}
const fetchSync = () => ({})

// type alias we'll use to signal whether or not to wrap the result in a promise
type PromiseIndicator = 'promise_indicator'

// conditional type that wraps the second argument in a promise if, and only if, the first type argument is set to PromiseIndicator
type ConditionalPromise<T, F> = T extends PromiseIndicator ? Promise<F> : F

// F = string is just a default; it could be anything other than PromiseIndicator
function makeClient<F = string>({fetcher}: any) {
  return {
    query: () => fetcher(`query`) as ConditionalPromise<F,QueryResult>,
    toc: () => fetcher(`toc`) as ConditionalPromise<F,TocResult>,
  }
}

makeClient(fetchSync)
makeClient<PromiseIndicator>(fetchSync)

I suspect there's a cleverer way to do this in your actual code based on the actual type of fetcher, but for the snippet you've provided this should work.

Playground

Upvotes: 1

Related Questions