Can the return type of a function be utilized as one of its argument types?

When attempting the following code:

function chain<A, B extends (input: A, loop: (input: A) => C) => any, C extends ReturnType<B>> (input: A, handler: B): C {
   const loop = (input: A): C => handler(input, loop);
   return loop(input);
}

chain('test', async (value, loop): Promise<string> => {
   const ready = await someExternalThing();
   if (ready) {
      return value;
   } else {
      return await loop(value + '!');
   }
});

Despite strict return type declaration for handler function, the final code displays a "any" return type for the loop function.

Is there a solution to address this problem?

Answer №1

This code snippet does exactly what you're looking for:

function createChain<Input, Result> (input: Input, handler: (input: Input, loop: (input: Input) => Result) => Result): Result {
   const loop = (input: Input): Result => handler(input, loop);
   return loop(input);
}

createChain<string, Promise<string>>('test', async (value, loop) => {
   const ready = await checkStatus();
   if (ready) {
      return value;
   } else {
      return await loop(value + '!');
   }
})

Make sure to specify the generic parameters when using CreateChain.

Answer №2

After reviewing the solutions provided by Gael J and Finomnis, I opted to create a simplified version that requires only one type parameter instead of two:

class Chainer<A> {
   run <B>(input: B, handler: (input: B, loop: (input: B) => A) => A) {
      const loop = (input: B) => handler(input, loop);
      return loop(input);
   };
}

new Chainer<Promise<string>>().run('test', async (value, loop) => {
   const ready = await someExternalThing();
   if (ready) {
      return value;
   } else {
      return await loop(value + '!');
   }
});

The approach involves using a class to hold the handler return type and utilizing the run method to infer the type of "input" from the first argument.

Alternate Technique

Instead of a class, another method involves using a wrapper function:

function chain<A> () {
   return <B>(input: B, handler: (input: B, loop: (input: B) => A) => A) => {
      const loop = (input: B) => handler(input, loop);
      return loop(input);
   };
}

chain<Promise<string>>()('test', async (value, loop) => {
   const ready = await someExternalThing();
   if (ready) {
      return value;
   } else {
      return await loop(value + '!');
   }
});

Answer №3

Unable to automatically determine the template parameters from the handler's return type, still seeking a solution.

If you're willing to provide the template parameters manually, there is a workaround:

let i = 0;

async function someExternalThing() {
    i += 1;
    return i > 3;
}

async function chain<VAL, RET>(
    input: VAL,
    handler: (input: VAL, loop: (input: VAL) => Promise<RET>) => Promise<RET>
): Promise<RET> {
    const loop = (input: VAL): Promise<RET> => handler(input, loop);
    return loop(input);
}

const result = chain<string, string>('test', async (value, loop) => {
    const ready = await someExternalThing();
    if (ready) {
        return value;
    } else {
        return await loop(value + '!');
    }
});

result.then((value) => {
    console.log(`Result: ${value}`)
})
Result: test!!!

Every type in the script is explicitly defined, no any or unknown.


Further exploration

This seems to be working:

async function chain<VAL, RET>(
    input: VAL,
    handler: (input: VAL, loop: (input: VAL) => Promise<RET>) => Promise<RET>
): Promise<RET> {
    const loop = (input: VAL): Promise<RET> => handler(input, loop);
    return loop(input);
}

const result = chain('test', async (value): Promise<string> => {
    return value;
});

However, when introducing the loop parameter, RET reverts to unknown. Appears to be a limitation of TypeScript's type inference mechanism at present. Potential for improvement in future iterations.

async function chain<VAL, RET>(
    input: VAL,
    handler: (input: VAL, loop: (input: VAL) => Promise<RET>) => Promise<RET>
): Promise<RET> {
    const loop = (input: VAL): Promise<RET> => handler(input, loop);
    return loop(input);
}

const result = chain('test', async (value, loop): Promise<string> => {
    return value;
});

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

What is the best way to utilize "exports" in package.json for TypeScript and nested submodules?

Looking to leverage the relatively new "exports" functionality in Node.js/package.json for the following setup: "exports": { ".": "./dist/index.js", "./foo": "./dist/path/to/foo.js" } so that ...

Tips for effectively transferring data between components in Angular 2

One of the challenges I'm facing is with my header component. It has a function that toggles a class on click, and it works perfectly within the header component. However, I now want to extend this functionality to my nav component in order to add cla ...

Deploying Angular to a shared drive can be done in a

My angular.json file contains the following line: "outputPath": "Y:\Sites\MySite", After running ng build, I encountered the error message: An unhandled exception occurred: ENOENT: no such file or directory, mkdir 'D:& ...

Implementing a 12-month display using material-ui components

Just starting out with ReactJs, TypeScript, and material-ui. Looking to display something similar to this design: https://i.stack.imgur.com/zIgUH.png Wondering if it's achievable with material-ui. If not, any suggestions for alternatives? Appreciate ...

Adding a type declaration to the severity property in React Alert - A guide to Typescript

I've developed a type declaration object for the incoming data, but no matter what I try to define as the type for the property "severity", it's not happy. The options it wants (as displayed below) don't seem feasible. I'm curious if th ...

The push action in NavController is not displaying Google Maps as expected

Trying to display a map upon clicking a button is proving challenging for me. It appears that the function NavController.push() does not work as expected, while NavController.setRoot() does. Despite not encountering any errors, I am unable to identify the ...

Embed the div within images of varying widths

I need help positioning a div in the bottom right corner of all images, regardless of their width. The issue I am facing is that when an image takes up 100% width, the div ends up in the center. How can I ensure the div stays in the lower right corner eve ...

Utilizing Material UI and TypeScript to effectively pass custom properties to styled components

Currently, I am utilizing TypeScript(v4.2.3) along with Material UI(v4.11.3), and my objective is to pass custom props to the styled component. import React from 'react'; import { IconButton, styled, } from '@material-ui/core'; con ...

The Recoil Nexus encountered an error: the element type provided is not valid. It should be a string for built-in components or a class/function for composite components, but an object was

Encountered the following error message: Error - Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. at ReactDOMServerRenderer.render ... This issue arose when integra ...

Using Typescript and ThreeJS, include new elements to the environment within the loader

Can someone help me with the following code snippet? export class LandingPageComponent implements OnInit { scene: THREE.Scene; (...) ngOnInit() { this.scene = new THREE.Scene(); var loader = new THREE.JSONLoader(); loader.load("../../assets/fire_lion.j ...

React Redux Saga doesn't trigger any actions

Currently, I am attempting to incorporate the following functionality: Users can successfully log in, but precisely after 5 seconds have passed, they are automatically logged out. My approach involves working with JSONWEBTOKEN. Here is my implementation u ...

Differentiating Service Class and Typescript Class in Angular 6

I am looking for a detailed explanation of service classes in Angular. From my perspective, both service classes and typescript classes serve the same purpose. So, what sets them apart from each other? ...

retrieve the router information from a location other than the router-outlet

I have set up my layout as shown below. I would like to have my components (each being a separate route) displayed inside mat-card-content. The issue arises when I try to dynamically change mat-card-title, as it is not within the router-outlet and does not ...

Creating a type in TypeScript with keys as values taken from another type

There is an object const navigatorNames: NavigatorNamesType = { homeNavigation: 'HomeNavigation', authNavigation: 'AuthNavigation' } with the type defined as type NavigatorNamesType = { homeNavigation: 'HomeNavigation ...

Exploring the ckeditor5-typing plugin within CKEditor

Currently, I am in the process of developing a soft keyboard using CKEditor. One part of this involves transforming text upon input (which I have completed) and occasionally needing to delete a key (where I am currently facing challenges). Below is the sni ...

Angular sets the required property only when the button is clicked

Is there a way to make a field required in Angular only when a button is clicked? Currently, the error message appears even before the user interacts with the field. I would like the error message "folder name is required" to only appear when the user cli ...

How to configure mat-sort-header in Angular Material for mat-table

My current table is created using Angular Material: <mat-table *ngIf="!waiting" class="table-general table-summary" #table [dataSource]="dataSource" matSort> <mat-header-row class="header_row" *matHeaderRowDef="headerKeys"></mat-header ...

Using external URLs with added tracking parameters in Ionic 2

I am looking to create a unique http link to an external URL, extracted from my JSON data, within the detail pages of my app. Currently, I have the inappbrowser plugin installed that functions with a static URL directing to apple.com. However, I would lik ...

Material UI offers a feature that allows for the grouping and auto-completion sorting

I am currently utilizing Material UI Autocomplete along with React to create a grouped autocomplete dropdown feature. Here is the dataset: let top100Films = [ { title: "The Shawshank Redemption", genre: "thriller" }, { title: " ...

Having trouble running `npm start` on my NodeJs project

Hi everyone, I could really use some help with the npm start command. I am currently working on a Node.js project with TypeScript on Windows 7-64, but I'm encountering errors when trying to start it. If you can assist, please check out the following ...