Conditional types fail to narrow down accurately

My function has the capability to do either of the following:

  1. Process a string search term and return a Promise
  2. Process a string search term along with a callback function, returning nothing (void)

Here is how the implementation looks like:

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 key issues that need addressing in this code:

  1. The type of cb remains as unknown even after being used within the if(cb) statement
  2. return res does not match the correct type, which should be Promise<Result[]>

What would be the proper way to define the types for such a function?

Answer №1

If you're interested, it's worth noting that achieving the same result without overloads is possible using conditionals in different ways. Just a heads up, here's one approach to accomplish it:

type Output = { id: string };

function dataFetcher(query: string): Promise<Output[]> {
  return Promise.resolve([{ id: "foo" }, { id: "bar" }]);
}

type CallbackFunction = (data: Output[]) => void;

type ReturnType<T> = [T] extends [CallbackFunction] ? void : Promise<Output[]>;
function searchData<T extends CallbackFunction | undefined>(
  query: string,
  callback?: T
): ReturnType<T> {
  const response = dataFetcher(query);

  if (callback) {
    response.then((result) => callback(result));
    return undefined as ReturnType<T>; // required for type assertion
  }

  return response as ReturnType<T>;
}

const searchPromise = searchData("keyword"); // ✅ Promise<Output[]>
const sampleSearch = searchData("keyword", () => undefined); // ✅ void

However, I personally wouldn't recommend this method as overloads tend to provide cleaner code.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The parameter type 'string | null' cannot be assigned to the argument type 'string'. The type 'null' is not compatible with the type 'string'.ts(2345)

Issue: The parameter type 'string | null' is not compatible with the expected type 'string'. The value 'null' is not a valid string.ts(2345) Error on Line: this.setSession(res.body._id, res.headers.get('x-access-token&ap ...

The search for global types seems to bewilder Mocha

Recently, I developed a small node application using Express and created a global interface called "LocalUser," which is essentially an express response with predefined locals. Instead of importing it everywhere, I opted to define a global type for it. dec ...

Incorrect line numbers displayed in component stack trace [TypeScript + React]

Challenge I am currently working on integrating an error boundary into my client-side React application. During development, I aim to showcase the error along with a stack trace within the browser window, similar to the error overlays found in create-reac ...

Tips for Verifying Browser Closure Status in an Angular 4 Application

In the process of developing my Angular Application, I have successfully implemented Login and Logout functionalities. Upon clicking logout, the user's status in the Database is updated using Laravel API. However, I am facing the challenge of handling ...

Unlocking the Power of Data Modelling in React and JavaScript

As a newcomer to React, I have been utilizing CRA in several production web apps. My background includes extensive work with Swift and familiarity with OOP concepts. Now, I am looking to incorporate models into my React projects. Here is an example of a s ...

Determine whether a variable is set as private or read-only

Consider having a class structure like this: export class ItemType{ readonly itemtype_id: number; public name :string; constructor(itemtype_id: number, name: string) { this.itemtype_id = itemtype_id; this.name = name; } } Now, there is ...

Activate the mat-menu within a child component using the parent component

Incorporating angular 6 along with angular-material, I am striving to trigger the matMenu by right-clicking in order to utilize it as a context menu. The present structure of my code is as follows. <span #contextMenuTrigger [matMenuTriggerFor]="context ...

Issue with accessing undefined property in Angular 2+ using Typescript

In my Angular 7 project, I am retrieving data from a service which looks like this: {name: "peter", datetime: 1557996975991} I have a method that is supposed to retrieve this data: myMethod() { this.myService.getdata().subscribe((res) = ...

Issue with running gulp ser on first attempt in SPFX

Every time I try running gulp serve, I encounter the following issue: Error: Unable to locate module '@rushstack/module-minifier-plugin' Please assist me with this problem. Thank you! ...

Tips for pausing execution until an asynchronous callback is finished?

Here are two methods that I have in my code. The issue I'm facing is with the `get` method which I am overriding from the Http module. The problem is that the authentication success callback is being triggered after the request has already been execu ...

Do not include properties with the NestJs condition

When responding to a Public route, I want to ensure that my users' emails are not exposed. However, I still need to access them from other routes that utilize a bearer JWT authentication system. This is the type of code I am looking to implement: @C ...

Setting up WebDriverIO to use ChromeDriver in non-headless mode on an Azure remote Linux agent can be achieved by following

Utilizing typescript in conjunction with cucumber and webdriverio to run automated tests on a remote Linux agent. The script runs smoothly in headless mode on the pipeline, but encounters errors when the headless option is removed from chromeOptions. Curr ...

Encountered an issue with fs.open where a non-literal argument was used at index 0 while utilizing a url

Attempting to achieve something similar in TypeScript: window.open(`https://somelink/certificate/${regNumber}?registrationNumber=${type}`); where the values of regNumber and type are constantly changing. ESLint is reporting an issue: Received fs.open with ...

Unable to bring in an exported class from a TypeScript file

I have a TypeScript file named foo.ts that contains an exported class called "Foo" export default class Foo{ } I am attempting to import this class into another file within the same directory import {Foo} from './foo'; However, I am encounter ...

Guide to connecting data from the backend to the frontend in the select option feature using Angular 9

I have a backend system where I store a number representing a selected object, which I am trying to display in a select option using Angular. Currently, the select option only displays a list of items that I have predefined in my TypeScript code using enu ...

EventListener cannot be removed

My TypeScript class is structured like this: class MyClass { let canvas: any; constructor(canvas: any) { this.canvas = canvas; this.canvas.requestPointerLock = this.canvas.requestPointerLock; document.exitPointerLock = ...

Solving issues with malfunctioning Angular Materials

I'm facing an issue with using angular materials in my angular application. No matter what I try, they just don't seem to work. After researching the problem online, I came across many similar cases where the solution was to "import the ...

Cypress: Unable to properly stub API with cy.intercept()

Whenever I utilize the cy.intercept() function, the API fails to stub. cy.intercept("GET", `${API}farm/list`, { body: { statusCode: 200, message: "Request successful", result: seededFarmList, }, }); The way I import the fixture file is as ...

Transform readonly properties into writable properties in TypeScript

I am facing an issue while trying to create a test helper function that simulates document key press events. Here is my current implementation: export const simulateKeyPress = (key: string) => { var e = new KeyboardEvent('keydown'); e.key ...

Using the angular2-cookie library in an Angular 2 project built on the rc5 version

Starting a new angular2 rc5 project, I wanted to import the angular2 cookie module. After installing the module with npm, I made changes to my angular-cli-build.js file : npm install angular2-cookie edited my angular-cli-build.js file : module.exports ...