Transform all fulfilled functions in Typescript into promises with the correct parameter types

Currently, I am diving into a codebase packed with numerous asynchronous API calls that include success options in their parameters. Here is a snippet:

declare function foo(_: { 
  success?: (_: string) => void, 
  fail?: () => void, 
}): void

declare function bar(_: { 
  success?: (_: string) => void, 
  fail?: () => void, 
  src?: string
}): void

declare function baz(_: {
 success?: (_: string) => void, 
 fail?: () => void, 
 expired: number 
}): void

declare function foobar(_: { 
  success?: (_: string) => void, 
  fail?: () => void, 
  token: string, 
  state: boolean 
}): void

My aim is to convert all these functions into promises using the following code:

interface Cont<R> {
  fail?: () => void
  success?: (_: R) => void
}

interface Suspendable<O> {
  (option: O): void
}

function suspend<R, O extends Cont<R>>(fn: Suspendable<O>) { 
  return async (opt: Omit<Omit<O, "success">, "fail">) => await new Promise<R>((resolve, _) => fn({
    ...opt,
    success: it => resolve(it),
    fail: ()=> resolve(undefined)
  }  as O )) // if any chance, I'd like to omit the `as O` but forgive it for now
}


(async () => {
  let _foo = await suspend(foo)({}) // good 
  let _bar = await suspend(bar)({}) // good
  let _baz = await suspend(baz)/* compile error here */ ({ expired: 100})
})()

Is there anything in TypeScript that I might have overlooked to assist me in capturing the true type of the O in the fn parameter? This way, I can properly constrain the parameters and eliminate compiler errors.

Answer №1

There are two possible approaches to consider here. The first option is to simplify by dropping one of the type parameters and inferring O to compute

R</code from it:</p>

<pre><code>function suspend<O extends Cont<any>>(fn: Suspendable<O>) {
  return async (opt: Omit<O, "success" | "fail">) =>
    await new Promise<Parameters<Exclude<O["success"], undefined>>[0]>(
      (resolve, _) => fn({
        ...opt,
        success: it => resolve(it),
        fail: () => resolve(undefined)
      } as O));
}

The calculation for R may appear complicated with

Parameters<Exclude<O["success"], undefined>>[0]
. However, this modification allows compilation without any errors:

(async () => {
  let _foo = await suspend(foo)({})
  let _bar = await suspend(bar)({})
  let _baz = await suspend(baz)({ expired: 2 })
})()

If you do not assert as O, an error related to O will arise due to potential mismatch in the properties success or fail:

declare function hmm(opt: {
  success?: (_: unknown) => void,
  fail?: () => string // narrower than specified in Cont<any>
}): void;
suspend(hmm);

This occurs because passing custom success and fail methods makes it uncertain that fn() behaves as intended. Therefore, asserting types is necessary.


The alternative is to preserve both type parameters and rely on the optional nature of success and fail methods:

function suspend2<O, R>(fn: Suspendable<O & Cont<R>>) {
  return async (opt: O) =>
    await new Promise<R>(
      (resolve, _) => fn({
        ...opt,
        success: (it: R) => resolve(it),
        fail: () => resolve(undefined)
      }));
}

Here, we deduce both `O` and `R` from the argument `fn` of type `Suspendable<O & Cont<R>>`. While the correct `R` is derived automatically, the full object type including `success` and `fail` remains for `O`. 

Although no assertions like as O are needed, since the value passed conforms to O & Cont<R>.

The following still functions as expected:

(async () => {
  let _foo = await suspend2(foo)({})
  let _bar = await suspend2(bar)({})
  let _baz = await suspend2(baz)({ expired: 2 })
})()

However, only due to the absence of a necessity for fail or success properties in O.


Hopefully, one of these alternatives proves helpful to your situation; best of luck!

Playground Link to 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 Google Maps feature encountered an error (ReferenceError: google is not defined)

Utilizing the Google Maps API on my website to display multiple locations has been successful so far. However, an issue arises when attempting to determine the distance between two sets of latitude and longitude coordinates - resulting in an error message ...

The Angular filter fails to remove elements from the list

I have implemented a filter function to remove objects from an array, but I am facing an issue. Challenge: Despite using the filter function, the elements are not being removed from the array as expected. Why are these objects still present in the array a ...

Setting properties of objects using call signatures nested within other objects

Having an object with a call signature and property: type MyDescribedFunction = { description: string () => boolean } In the scenario where creating an instance is not possible in the usual manner, the following approach ensures compiler satisf ...

Error message "The process is not defined during the Cypress in Angular with Cucumber process."

Exploring Cypress for end-to-end testing in an Angular 12 project with Cucumber and TypeScript has been quite the journey. Cypress launches successfully using npx cypress open, displaying the feature file I've created: https://i.sstatic.net/Q5ld8.png ...

Is it Beneficial to Combine jQuery with TypeScript in Angular 7?

Our school project team is venturing into the world of Angular and TypeScript, but we all admit to being newbies in this area. I've mainly focused on the design aspect of our project and left the coding to my teammates. However, I recently completed ...

Unlocking the power of React using TypeScript for optimal event typing

I need assistance with properly typing events in TypeScript. Consider the following function: import * as React from 'react'; someHandler = (event: React.SyntheticEvent<HTMLInputElement> | React.KeyboardEvent<HTMLInputElement>) =&g ...

Utilizing Foundation and jQuery in a Next.js web development project

I've been attempting to incorporate Zurb Foundation's scripts into my next js application, but I keep encountering an error message when trying to include the Foundation core. The error I'm seeing is: /Users/alasdair_macrae/Sites/merlin/spa_ ...

The data type returned by a method is determined by the optional parameter specified in the

I have a situation where I need to create a class with a constructor: class Sample<T> { constructor(private item: T, private list?: T[]) {} } and now I want to add a method called some that should return: Promise<T> if the parameter list ...

Exploring the differences: Async await, Promises, and Mapping

When faced with the decision between promises, async awaits, and mapping operators like concatMap, how do you determine which one to use? Specifically, I am working on a scenario where I need to make an http call to my backend, followed by another http ca ...

Intellisense capabilities within the Gruntfile.js

Is it a feasible option to enable intellisense functionality within a Gruntfile? Given that 'grunt' is not defined globally but serves as a parameter in the Gruntfile, VSCode may interpret it as an unspecified function parameter 'any'. ...

What is the method for dynamically selecting generics from a function in a union type using Typescript?

Suppose there is a function defined as follows: type FooParams<Params extends unknown[], Result> = { name: string, request: (...params: Params) => Promise<Result> } const foo = <Params extends unknown[], Result>(params: FooParams ...

Does the JavaScript Amazon Cognito Identity SDK offer support for the Authorization Code Grant flow?

Is there a way to configure and utilize the Amazon Cognito Identity SDK for JavaScript in order to implement the Authorization Code Grant flow instead of the Implicit Grant flow? It appears that the SDK only supports Implicit Grant, which means that a Clie ...

Exploring the benefits of thrift integration in Angular 2

Currently, I am working with the thrift protocol and have developed a basic app using Angular2. I create .js files by running thrift --gen js:ts file.thrift. Can anyone provide guidance on integrating Thrift into an Angular2 application? ...

Tips for altering promises within nested for loops?

Presented below is a complex function that aims to extract model variants from a csv file, which are stored as strings in an array. The path of this csv file depends on information obtained from other csv files, hence the need for looping. The csvService. ...

Missing data list entries for next js server actions

After successfully running my add function, I noticed that the data I added earlier is not being reflected in the list when I check. import React, { useEffect, useState } from "react"; import { createPost } from "./actions"; import { SubmitButton } from ". ...

Error encountered in Nest.js tests due to dependency injection issues between modules. The module 'src/foo/foo.module' cannot be located from 'bar/bar.service.spec.ts'

Encountering an error message Cannot find module 'src/foo/foo.module' from 'bar/bar.service.spec.ts' while testing a service that relies on another module. I am facing difficulty in setting up the test scenario for a Nest.js project wi ...

Using ts-loader with Webpack 2 will result in compatibility issues

Lately, I've been working on setting up a basic Angular 2 (TypeScript) application with Webpack 2 for bundling. However, I'm encountering numerous errors when using ts-loader to process TypeScript (.ts) files. It seems like ts-loader is not excl ...

Node.js: Crafting appropriate responses for errors in asynchronous operations or promises

Looking for a simple solution to handle all async/Promise errors effectively? The typical pattern of: app.use(function (err, req, res, next) { ... }); does not cover async errors and the search for an alternative has been challenging. I came across a ...

Is there a way to specify object keys in alignment with a specific pattern that allows for a variety of different combinations

I am seeking a way to restrict an object to only contain keys that adhere to a specific pattern. The pattern I require is: "{integer}a+{integer}c". An example of how it would be structured is as follows: { "2a+1c": { // ... } } Is there a ...

Refresh Ionic 2 Platform

I'm currently working on an Ionic 2 app and whenever I make a change to the .ts code, I find myself having to go through a tedious process. This involves removing the platform, adding the Android platform again, and then running the app in Android or ...