Mastering the Art of Dynamically Assigning Types in TypeScript

Recently, I encountered a challenge with wrapper function types.

I am looking to convert functions with callbacks into promise-based functions. Most of these functions have a similar structure:

func({
  success:....,
  fail:....,
  complete:...
})

So, I created a wrapper function:

function toAsync<
        OptType
        SuccesResType,
        FailResType>(fn:(opt:OptType)=>any):(opt:OptType)=>Promise<SuccesResType>{
  
  return async (opt:OptType):Promise<SuccesResType>=>{
    return new Promise<SuccesResType>((resolve,reject)=>{
      fn({
        ...(opt || {}),
        success(res:SuccesResType){
          resolve(res)
        },
        fail(res:FailResType){
          reject(res)
        }
      } as unknown as OptType)
    })
  }
}

However, I faced an issue where the SuccessResType represents the parameter type of opt.success, and FailResType is for opt.fail.

My problem lies in defining the correct type hint for my toAsync wrapper function when using it like this:

const fn = toAsync(fn)

Instead of repeating certain steps such as:

type fnOptType = Parameters<typeof fn>[0]
type fnSuccessRestype = Parameters<fnOptType.SuccessCallback>[0]
type fnFailResType = Parameters<fnOptType.FailCallback>[0]
type fn = toAsync<fnOptType,fnSuccessRestype,fnFailResType>(fn)

Currently, if I use toAsync like const fn = toAsync(fn), no hint option will be provided unless I manually specify the generic type.

Update: For fn, its option type can vary, such as:

function request(opt:{
    fail?:(res:RequestFailResType)=>any;
    success?:(res:RequestSuccessResType)=>any;
    complete?:(res:RequestCompleteResType)=>any;
    url:string
}):any{
    // do things here
}

function locationQuery(opt:{
    fail?:(res:QueryFailResType)=>any;
    success?:(res:QuerySuccessResType)=>any;
    complete?:(res:QueryCompleteResType)=>any;
    lat:number,
    log:number
}):any{
    // do things here
}

toAsync needs to accommodate these type variations, which appears to be quite a significant compatibility requirement.

Considering the numerous functions I have structured in this way, I am seeking a more efficient method for achieving backward type support.

Answer №1

A potential solution is to introduce generics to the OptType interface as shown below:

interface OptType <T1, T2> {
  complete: () => void
  success: (successResult: T1) => void
  fail: (failResult: T2) => void
}

This allows for passing generic arguments to the toAsync function like so:

fn: (opt: OptType<SuccessResType, FailResType>) => any

In this setup, the types for SuccessResType and FailResType can be automatically inferred. The overall structure of the solution becomes:

declare function myFunc(opts: MyFuncOptType): void

interface OptType <T1, T2, T3> {
  complete?: (res: T3) => void
  success?: (successResult: T1) => void
  fail?: (failResult: T2) => void
}

...
(Please refer to the Playground link for the full implementation)

Keep in mind that unnecessary types have been removed for clarity. Feel free to rename T1 and T2 as needed.

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

Universal loading screen across all components

I am currently implementing a loading screen for this component in conjunction with the fetch method. My concern is whether I will need to replicate the same loading logic used in the example for every component that utilizes the fetch() method, or if the ...

CAUTION: The presence of numerous imports of Three.js

I encountered a warning when attempting to utilize both @react-three/fiber and @react-three/drei in my project. https://i.sstatic.net/SkM2K.png Steps to replicate npx create-react-app reproduced-warning cd reproduced-warning npm install three @react-thre ...

The button click event is failing to trigger the jQuery script

Attempting to retrieve selected data from a table using Bootstrap and jQuery. After selecting checkboxes and clicking the "Add to Cart" button, I need to get the chosen data from the table. Here is a snippet of my code: <!DOCTYPE html PUBLIC "-//W3C ...

How can we export data to excel using react-export-excel while ensuring certain columns are hidden?

Greetings! I believe the title gives you a clear idea of my dilemma. When exporting data, there are no errors - my goal is to hide the Excel column when the checkbox is unchecked or false, and display it when the checkbox is checked or true during export. ...

Steps for creating a basic prioritized event listener system in JavaScript

I am currently working on an event/listener manager that has the following function: var addListener = function(event, listener) { myListeners[event].push(listener); //assume this code works } However, I now need to modify it so that it appears a ...

Shiny silver finish using three.js technology

I have recently started exploring three.js and am attempting to replicate a cube that looks like this: https://i.sstatic.net/CO9Eq.jpg One aspect I'm struggling with is creating the material for my cube. The material appears to be a silver polished fi ...

When incorporating a CDN in an HTML file, an error message may appear stating that browserRouter is not defined

I am attempting to create a single HTML page with React and React-router-dom. Initially, I used unpkg.com to import the necessary libraries, and I was able to load the page with basic React components. However, when I tried incorporating React-router-dom ...

Encountering an error when attempting to generate a production build after running the npm install command with the

After adding the brotli-webpack-plugin as a devDependency, I encountered an issue when attempting to generate a production build using npm run build (which internally runs next build). The error message displayed was: Error: Cannot find module 'bro ...

Experiencing unexpected output from Angular model class method

I have developed a user-friendly Invoicing & Inventory management application that showcases a list of invoices for each customer. However, there seems to be an issue with the calculation of the Grand Total function, which I am struggling to rectify due to ...

Ways to select a random row from a MySQL database

<button onclick="myUniqueFunction()">Press here!</button> <?php function myUniqueFunction() { $con = mysqli_connect("mysql.hostinger.no", "u452849516_altge", "password", "u452849516_altge"); $query = "S ...

Ways to expose an object in a Node module

One of my modules relies on a parsing functionality, with other modules needing to query the values provided by this parser. I have some key questions regarding its design: How should I approach building it in terms of design aspects? Which method should ...

Maximizing Angular and Require's Potential with r.js

I'm facing some challenges while setting up r.js with require in my Angular application. As I am new to AMD, solving this issue might be a simple task for someone experienced. However, I need to maintain the current directory structure as it allows me ...

Using external scripts that require access to the DOM and window object within a Javascript/Node.js Azure Function: Best practices

I am attempting to integrate external JavaScript into an Azure Function that uses the JavaScript/Node.js flavor. The external JavaScript library I need to use, Kendo, relies on a DOM and a window. To achieve this, I initially experimented with JSDOM, but I ...

Methods to Maintain Consistent HTML Table Dimensions utilizing DOM

I am facing an issue with shuffling a table that contains images. The table has 4 columns and 2 rows. To shuffle the table, I use the following code: function sortTable() { // Conveniently getting the parent table let table = document.getElementById("i ...

I encountered an issue when trying to dynamically add a text field in Angular 2. The error message received was "ERROR TypeError: Cannot read property '0' of

I am currently utilizing Angular2 in my project, and I am attempting to dynamically add a text field. However, I keep encountering an error: Error Message (TS): ngOnInit() { this.myForm = this._fb.group({ myArray: this._fb.array([ ...

What is the trick to positioning the dice above the green circle and below the line, as shown in the image?

I'm struggling to position the large green circle and the dice image on top of each other as well as below each other. How can I achieve the desired layout shown in this image? Here's what I have managed to do so far https://i.sstatic.net/ryIPl.p ...

Tips for achieving functionality with ng-click in an Angular DataTables column creator

Here is the code snippet I am working with: Controller: vm.dtColumns = [ DTColumnBuilder.newColumn('product_code').withTitle('Code'), DTColumnBuilder.newColumn('product_name').withTitle('Name'), ...

AngularJS $q - pausing execution to synchronize all promises

I've encountered a challenge that I haven't been able to solve through online searches. In my project, I am using a library for selecting items and performing custom modifications through callback functions. However, I need to execute some async ...

Ajax requests frequently result in alert messages being displayed

I'm struggling with an issue in my AJAX code. AJAX <script type="text/javascript"> var stopTime = 0; var scoreCheck = function () { $.ajax({ url: 'http://127.0.0.1/ProgVsProg/main/checkScoreRoundOne', success:functi ...

Why does refreshing a page in AngularJS cause $rootScope values to disappear?

On my local route http://localhost:9000/#/deviceDetail/, there is a controller that handles the view. Before navigating to this view, I set certain variables on the $rootScope (such as $rootScope.dashboards). While on the view, I have access to the dashbo ...