What is the best way to restrict the return type of a function depending on the argument value?

Is there a way to consolidate the methods of an interface into a single function with an additional operation parameter? Despite being able to call the resulting function as expected, I'm struggling to narrow the return type within the function itself.

While I found the answer to a previous question of mine helpful in narrowing parameter types, the ability to narrow the return type inside the function remains a mystery:

interface Machine {
    doSomething: (arg: number) => string;
    doSomethingElse: (args: boolean) => number;
};

type MachineParameters = {
    [K in keyof Machine]: [K, ...Parameters<Machine[K]>];
}[keyof Machine];

const execute = <T extends MachineParameters>(...args: T): ReturnType<Machine[T[0]]> => {
    switch (args[0]) {
        case "doSomething":
            // Error: Type '`${number}`' is not assignable to type 'MachineReturnTypes[T[0]]'.
            return `${args[1]}`;
        case "doSomethingElse":
            // Error: Type 'number' is not assignable to type 'MachineReturnTypes[T[0]]'.
            return Number(args[1]);
        default:
            throw new Error("Unknown operation");
    }
};

// Works: string
const x = execute("doSomething", 1);
// Works: number
const y = execute("doSomethingElse", true);

Playground

Answer №1

To effectively narrow down the potential multiple overloads within a function, I employ a clever trick of declaring the implementation arguments as a union of tuples representing the different overloads. This may seem a bit repetitive, but TypeScript excels in narrowing tuples based on member types or sizes.

// overloads
function execute(operation: 'doSomething', arg: number): string;
function execute(operation: 'doSomethingElse', arg: boolean): number;
// implementation
function execute(
    ...args: [operation: 'doSomething', arg: number] | [operation: 'doSomethingElse', arg: boolean]
): string | number {
    switch (args[0]) {
        case "doSomething":
            // args is narrowed to [operation: 'doSomething', arg: number]
            return `${args[1]}`;
        case "doSomethingElse": 
            // args is narrowed to [operation: 'doSomethingElse', arg: boolean]
            return Number(args[1]);
        default:
            // args is narrowed to never
            throw new Error(`Unknown operation ${args[0]}`);
    }
};

Run it in Playground

Answer №2

To define different return types based on the operation parameter, you can make use of function overloads:

interface Machine {
  ...
}

function execute(operation: 'doSomething', arg: number): string;
function execute(operation: 'doSomethingElse', arg: boolean): number;
function execute(operation: keyof Machine, args: any): any {
  ...
}

Explore playground example

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

Utilize the class type of a method parameter as the method type for another parameter

Here's a quick example illustrating my desired functionality. // Every time, the ACL class will have a different name such as "UsersACL", etc. export class EventsACL { test(): { read: true, write: true } { } } // This function acts ...

A guide to resolving the error "Cannot read properties of undefined (reading 'length')" while implementing pagination with react, Material-UI, and TypeScript

As I work on my code, my goal is to display a limited number of cards per page based on the data stored in a JSON file. I anticipated that clicking on the ">" or "<" icon would navigate to the next or previous page respectively, updating the displaye ...

React: The Material-UI autocomplete input, controlled with the React Hook Form `<controller>` component, experiences issues when the `multiple` prop is set to `true`

Currently facing challenges with managing an autocomplete MUI component using the <controller> component in react-hook-form. Take a look at the code snippet below: <Controller control={control} name="rooms" render={({ field }) =&g ...

In Typescript, it is not possible to use generics as a function parameter in a

Looking for a solution regarding passing the union of two specific samples of generics to a function. The function in question is as follows: function myCommonFunc<T>({ data, render, }: { data: T; render: (data: T) => number; }) { return ...

Example of TypeScript Ambient Namespace Usage

The Namespaces chapter provides an example involving D3.d.ts that I find puzzling. Here is the complete example: declare namespace D3 { export interface Selectors { select: { (selector: string): Selection; (element: ...

Creating a service in AngularJS 1.8 with ES6 modules that acts as a bridge to a class based API interface

As I continue to enhance a codebase that originally consisted of a mix of different versions of AngularJs and some unstructured code utilizing various versions of a software API, I encounter an interesting quirk. It appears that this API is accessible thro ...

Is it possible to modify the number format of an input field while in Antd's table-row-edit mode?

I am currently utilizing the Table Component of Ant Design v2.x and unfortunately, I cannot conduct an upgrade. My concern lies with the inconsistent formatting of numbers in row-edit mode. In Display mode, I have German formatting (which is desired), but ...

I'm encountering an issue where Typescript is unable to locate the Firebase package that I

I have a TypeScript project that consists of multiple .ts files which need to be compiled into .js files for use in other projects. One of the files requires the firebase package but it's not being found. The package is installed and located inside t ...

Guide to updating the canvas in Chart.js based on a user-defined x-axis range

What I currently have: My chart.js canvas displays values on the x-axis ranging from 1 to 9. Users can input a new range to view a different scope, with default limits set at start = 3 and end = 6 in my repository. I already have a function that restrict ...

Having difficulty authenticating Slack requests

I'm currently working on a project to develop a Slack bot using the events API for an experiment at my job. I am facing challenges in verifying the request and can't seem to pinpoint where I'm making a mistake. The bot is being built using ...

Enhancing IntelliSense to recognize exports specified in package.json

I have a package.json file where I define various scripts to be exported using the exports field. "exports": { ".": { "default": "./dist/main.es.js", "require": "./dist/main.cjs.js", ...

What is preventing TypeScript from automatically inferring the type of an argument in a generic function that utilizes `keyof`?

What causes the error in this code snippet? type MyType = { a: string, b: string } function cantInfer<In, Out>(fn: (i: In) => Out, i: In) { } function myFunction<K extends keyof MyType>(key: K): string { return ''; } ...

Is there a way to monitor user engagement within my app without depending on external analytics platforms?

I'm looking to enhance the user-friendliness of my applications deployed on the Play Store by tracking users' interactions. Specifically, I want to keep track of: Screen Time: Monitoring how much time users spend on each screen. Clicks: Tracking ...

Determining the parameter type of an overloaded callback: A comprehensive guide

I am currently working on creating a type-safe callback function in TypeScript with a Node.js style. My goal is to define the err parameter as an Error if it exists, or the data parameter as T if not. When I utilize the following code: export interface S ...

Upon successfully maneuvering vendors who fail to load the NEXT.JS Link

Here is an image that is not displaying properly. The navbar's responsiveness seems to be causing the issue. return ( <Link key={index} href={'/'+item.id} > <li className="nav-item dropdown position-stati ...

Executing a series of asynchronous HTTP calls in Angular until a specific condition is satisfied

In Angular, I am making an HTTP call that returns a promise. Currently, I am refreshing the call using setTimeout at regular intervals. Are there any built-in functions or design patterns available to handle this task more efficiently? ...

Error: Tried to modify a property that is read-only while using the useRef() hook in React Native with Typescript

https://i.sstatic.net/jhhAN.pngI'm facing an issue while attempting to utilize a useRef hook for a scrollview and pan gesture handler to share a common ref. Upon initializing the useRef() hook and passing it to both components, I encounter an error th ...

Mastering the art of correctly utilizing splice and slice

I'm having trouble identifying the issue in my code. Despite reading numerous articles on slice and splice, I am unable to achieve the desired outcome in my Angular project (not using both methods simultaneously). The results are not as expected. Belo ...

Nestjs is throwing an UnhandledPromiseRejectionWarning due to a TypeError saying that the function this.flushLogs is not recognized

Looking to dive into the world of microservices using kafka and nestjs, but encountering an error message like the one below: [Nest] 61226 - 07/18/2021, 12:12:16 PM [NestFactory] Starting Nest application... [Nest] 61226 - 07/18/2021, 12:12:16 PM [ ...

Implementing query parameters in a Deno controller

I developed a couple of APIs for a Deno Proof of Concept. This is the route implementation: const router = new Router() router.get('/posts', getPosts) .get('/posts/:id', getPostsById) In the second route, I successfully retriev ...