Home > Mobile >  Conditional types don't narrow properly
Conditional types don't narrow properly

Time:02-15

I have a function that either:

  1. accepts a string search term, returning a Promise
  2. accepts a string search term and a callback function, returning nothing (void)

Here is my implementation:

function fetcher(term: string): Promise<Result[]> {
    return Promise.resolve([{id: 'foo'}, {id: 'bar'}])
}

type Callback = (results: Result[]) => void
function search<T>(term: string, cb?: T ): T extends Callback ?  void : Promise<Result[]> {
    const res = fetcher(term)

    if(cb) {
        res.then(data => cb(data)) // ❌  Type 'unknown' has no call signatures.(2349)
    } 

    return res // ❌ Type 'Promise<Result[]>' is not assignable to type 'T extends Callback ? Promise<Result[]> : void'.
}

const promise = search('key') // ✅ Promise<Result[]>
const v = search('key', () => {})  // ✅ void

There are two issues with this:

  1. The type of cb is still unknown after if(cb)
  2. return res cannot match the right type, i.e. Promise<Result[]>

How should I properly type such a function?

CodePudding user response:

As mentioned in the comments, conditional types don't really work used like this. To achieve the outcome you are looking for you need to use function overloading (as others have said)

Here is a suggested implementation:

function search(term: string): Promise<Result[]>
function search(term: string, cb: Callback): void
function search(term: string, cb?: Callback) {
    const res = fetcher(term)

    if(cb) {
        res.then(data => cb(data))
        return
    } 

    return res
}

CodePudding user response:

Here is a way I found that I can get rid of the type errors

type Result = {id: string}

function fetcher(term: string): Promise<Result[]> {
    return Promise.resolve([{id: 'foo'}, {id: 'bar'}])
}

type Callback = (results: Result[]) => void
function search<T extends Callback>(term: string, cb?: T ): T extends Callback ?  void : Promise<Result[]> {
    const res = fetcher(term)

    if(cb) {
        res.then(data => cb(data)) // ✅
    } 

    return res as Promise<Result[]> & void// ✅
}

const promise = search('key') // ✅ Promise<Result[]>
const v = search('key', () => {})  // ✅ void

Of course I am using unsafe type assertion but function overloads is as unsafe us type assertion so

  • Related