Dealing with Promise Wrapper Already Declared in TypeScript Return TypeMismatch

Here is a snippet of TypeScript code I am working with:

type Chain = 'eth'

export type ApiMethods = {
  getToken: {
    path: 'tokens/'
    (args: {
      chain: Chain
      tokenAddress: EthereumAddress
    }): string
  }

  getRank: {
    path: 'rank/wallets/'
    (): string
  }
}

type MethodParams<K extends keyof ApiMethods> = Parameters<ApiMethods[K]>[0]

type Api = {
  [K in keyof ApiMethods]: MethodParams<K> extends undefined
    ? {path: ApiMethods[K]['path'], (): Promise<ReturnType<ApiMethods[K]>>}
    : {path: ApiMethods[K]['path'], (args: MethodParams<K>): Promise<ReturnType<ApiMethods[K]>>}
}

type ApiParameters<K extends keyof Api> = Parameters<Api[K]>[0]

type ApiPath<K extends keyof Api, T = ApiParameters<K>> = T extends {chain: Chain} 
    ? `${Api[K]['path']}${T['chain']}/`
    : `${Api[K]['path']}`

type Payload<K extends keyof Api> = Omit<Parameters<Api[K]>[0], 'chain'>

export type ApiClientOptions = {
  apiRoot?: string
}

class ApiClient {
  private readonly options: Required<ApiClientOptions>

  constructor(options: ApiClientOptions = {}) {
    const defaultOptions = {
      apiRoot: 'https://api.com/',
    };
    this.options = { ...defaultOptions, ...options };
  }

  private async callApi<K extends keyof Api>(path: ApiPath<K>, payload: Payload<K>): ReturnType<Api[K]> {

  }
}

The Api type encapsulates the function return type within a Promise.

However, when invoking the callApi function, an error occurs:

 The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<ReturnType<Api[K]>>'?

How can this error occur if the return type of the Api type is already wrapped in a Promise? Any suggestions on how to resolve this issue?

Answer №1

TypeScript has a strict requirement that any async function must explicitly return the global Promise type. This rule is more rigid than regular functions because async functions are not allowed to return a custom subtype of Promise (refer to microsoft/TypeScript#35856). The compiler seems to enforce this strictly, even though the generic type ReturnType<Api[K]> does not necessarily have to be a Promise, leading to complaints.

To work around this issue, you can wrap your type with

Promise<Awaited<⋯>>
. If your type is already a Promise, it won't affect anything since the Awaited type unwraps Promises. However, if it's not a Promise, this approach will convert it into one:

private async callApi<K extends keyof Api>(
  path: ApiPath<K>,
  payload: Payload<K>
): Promise<Awaited<ReturnType<Api[K]>>> {⋯}

For a closer look and testing in action, visit the 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

Will the async pipe activate onPush change detection in Angular?

I have searched various sources for the question above, but I am finding conflicting answers. For example, on Angular University's website, it is mentioned that change detection is triggered when the async pipe receives a new observable value. However ...

Retrieve a collection of Firebase records using a specific query parameter

I'm currently developing a web app with Angular 2 and facing an issue with retrieving data from Firebase based on specific conditions in the child node. Here's how my Firebase structure looks like: I need to extract the entry for the one with app ...

Bring in Event Types from React using TypeScript

Is there a way to import Event Types from React and use them in Material-ui? Specifically, I am looking for guidance on how to import KeyboardEvent so that it can be utilized for onKeyDown callback type annotation. I have examined the .d.ts file of Mater ...

Attempting to utilize a namespace-style import for calling or constructing purposes will result in a runtime failure

Using TypeScript 2.7.2 and VSCode version 1.21 with @types/express, I encountered an issue where in certain cases VSCode would report errors like: A namespace-style import cannot be called or constructed, and will cause a failure at runtime. Interestingly ...

What is the best way to create a data type that enables unrestricted index retrieval while ensuring that the value retrieved will never be

By enabling the noUncheckedIndexedAccess option in TypeScript, we ensure that when accessing object properties with arbitrary keys, the value type includes both the specified type and undefined. This behavior is generally appropriate as it aligns with run ...

Need to import Vue component TWICE

My question is simple: why do I need to import the components twice in the code below for it to function properly? In my current restricted environment, I am unable to utilize Webpack, .vue Single File Components, or npm. Despite these limitations, I mana ...

Removing the id attribute in the innerHTML property of Angular 4

After making an API call to load HTML content into an element by updating the innerHTML, everything seems to be working fine except for one issue - the id attribute gets removed from the loaded content. Here is the component code: content: string; @Vie ...

Using the spread operator for type checking of generics is overly broad

While experimenting with interface inheritance and generics, I came across a peculiar behavior that might lead to runtime problems. This issue is observed in the latest release of TypeScript, version 5.0.3. Essentially, it seems that a function accepting a ...

Oops! It seems like there is an issue with reading the property 'filter' of an undefined object. Any ideas on how to resolve this error

Having an issue with a custom filter that is causing an error "ERROR TypeError: Cannot read property 'filter' of undefined". I need help fixing this as it's preventing anything from rendering on the page. Any suggestions on what changes I sh ...

Tips and tricks for setting up a functional react select component using Material UI

Having an issue with React Select material UI where the dropdown select is not functioning properly. The popup does not open up as expected. I am looking to display react select in a modal dialog box. import MenuItem from "@mui/material/MenuItem" ...

Matching the types of method parameters to the types of callback function parameters in Typescript generics

I am currently working on creating a class that takes a function as a parameter in the constructor. The function can have arguments of any type. My goal is to add a method to the class that also accepts the same arguments as the function parameter, essenti ...

Fixing the "tl-node is not recognized" error in VS Code and TypeScript

After installing VS Code, I am struggling to figure out how to compile TypeScript code in VSCODE. Some resources mention that VSCODE includes a "stable" version of TypeScript, while others suggest installing TypeScript separately. When I try to write the ...

The error message indicates that the property `v.context.$implicit` is not callable

I am a beginner with Typescript and it has only been 3 days. I am trying to access data from Firebase and display it in a list. However, I keep encountering an error when trying to navigate to another page using (Click) ="item ()". Can someone point out wh ...

Determining if an object aligns with a specific type in Typescript

Hey there, I've got a little dilemma. Imagine I have a type called A: type A = { prop1: string, prop2: { prop3: string } } Now, let's say I'm getting a JSON object from an outside service and I need to check if that JSO ...

What properties are missing from Three.js Object3D - isMesh, Material, and Geometry?

Currently, I am working with three.js version r97 and Angular 7. While I can successfully run and serve the application locally, I encounter type errors when attempting to build for production. Error TS2339: Property 'isMesh' does not exist on ...

useEffect does not trigger a rerender on the primary parent component

I am facing an issue where the main parent component does not re-render when I change the state 'click button' in another component while using useEffect. Oddly enough, the main <App /> component only re-renders properly when I reload the p ...

Error message is not shown by React Material UI OutlinedInput

Using React and material UI to show an outlined input. I can successfully display an error by setting the error prop to true, but I encountered a problem when trying to include a message using the helperText prop: <OutlinedInput margin="dense&quo ...

There are no call signatures available for the unspecified type when attempting to extract callable keys from a union

When attempting to write a legacy function within our codebase that invokes methods on certain objects while also handling errors, I encountered difficulty involving the accuracy of the return type. The existing solution outlined below is effective at cons ...

Ensure that any modifications made to an Angular service are reflected across all components that rely on that service

I am currently in the process of replicating a platform known as Kualitee.com, which serves as a test-management tool utilized by QA Engineers. Within Kualitee, users can access multiple projects, each containing various test cases and team members. The ab ...

Issue: the module '@raruto/leaflet-elevation' does not include the expected export 'control' as imported under the alias 'L' . This results in an error message indicating the absence of exports within the module

Looking for guidance on adding a custom Leaflet package to my Angular application called "leaflet-elevation". The package can be found at: https://github.com/Raruto/leaflet-elevation I have attempted to integrate it by running the command: npm i @raruto/ ...