What is the best way to develop a function that can take in either a promise or a value and output the same type as the input parameter?

I'm currently working on a TypeScript function that can handle either an A type or a Promise<A>. In this case, A represents a specific concrete type, and the function should return the same type. If it receives a Promise<A>, then the return type will also be Promise<A>, and vice versa.

I tried creating a conditional return type based on the input type like this:

func<T = A | Promise<A>, R = T extends A ? A : Promise<A>>(arg: T): R
. However, when I use a type guard to assert that arg is Promise<A>, the type becomes T & Promise<A> in one branch and remains as T in the other. I expected it to become A and Promise<A>.

On the other hand, if I create a function like

func(arg: A | Promise<A>): void
, the type guard works correctly. It identifies the actual types as A in one branch and Promise<A> in the other. However, I'm struggling to apply this typing to the return value.

interface A {
  x(): void;
}

interface Promise<T> {
}

function isPromise<T>(obj: unknown): obj is Promise<T> {
  return typeof (obj as Promise<T>).then === "function";
}

const a: A = {
  x: () => {}
};
const promiseA = Promise.resolve(a);

function doSomething<T = A | Promise<A>, R = T extends Promise<A> ? Promise<A> : A>(arg: T): R {
  if (isPromise<A>(arg)) {
    const type: Promise<A> = arg; 
    return Promise.resolve(a) as R;
  } else {
    const type: A = arg;
    return a;
  }
}

function doSomething2(arg: A | Promise<A>): void {
  if (isPromise<A>(arg)) {
    const type: Promise<A> = arg; 
  } else {
    const type: A = arg;
  }
}

const anA: A = doSomething(a);
const aPromiseA: Promise<A> = doSomething(promiseA);
const aPromiseB: Promise<A> = doSomething(a);
const anB: A = doSomething(promiseA);

Playground

Answer №1

You may be making things too complicated. While you could utilize Conditional Types, there would be a need to assert return types in that scenario.

At times, incorporating Function Overloads can streamline the process significantly because the correct types are automatically selected based on the overload being called.

interface B {}
interface Promise<U> {}

function isPromise<U>(obj: unknown): obj is Promise<U> {
  return typeof (obj as Promise<U>).then === "function";
}

const b: B = {};

function performAction(arg: Promise<B>): Promise<B>;
function performAction(arg: B): B;
function performAction(arg: B | Promise<B>) {
  if (isPromise<B>(arg)) {
    // functionality ...
    return arg;
  } else {
    // functionality ...
    return arg;
  }
}

const bB: B = performAction(b);
//    ^? const bB: B
const bPromiseB = performAction(Promise.resolve(b));
//    ^? const bPromiseB: Promise<B>

TypeScript Playground

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

Encountering build issues in my next.js application post updating to version 12.#.# and implementing Typescript

In my next.js application, I recently upgraded to version 10 and added TypeScript to the mix. Despite ironing out all issues during development, I encountered errors when running yarn next build due to my use of the keyword interface. ./src/components/ ...

The useForm function from react-hook-form is triggered each time a page is routed in Nextjs

Hey there, I'm just starting out with Next.js (v14) and I'm trying to create a multi-page form using react-hook-form. But I'm encountering an issue where the useForm function is being executed every time, and the defaultValues are being set ...

Gathering adorned categorizations (sans any listed category divisions)

My current setup involves an event dispatcher class that triggers listeners on specified occurrences. I've successfully implemented registering event listeners via decorators, but I feel like there may be a better solution out there. At the moment, e ...

Discover the contents of an Object's key in TypeScript

I currently have a variable of type object. let ref_schema_data: object The value of ref_schema_data: { '$schema': 'http://json-schema.org/draft-07/schema', '$id': 'tc_io_schema_top.json', allOf: [ { type: &a ...

What is the best way to define the type of an object in TypeScript when passing it to a function

I am currently working on a function that accepts an object of keys with values that have specific types. The type for one field is determined by the type of another field in the same object. Here is the code: // Consider this Alpha type and echo function. ...

I'm curious about the significance of this in Angular. Can you clarify what type of data this is referring

Can anyone explain the meaning of this specific type declaration? type newtype = (state: EntityState<IEntities>) => IEntities[]; ...

What is the process to activate a function within a component once a service method has been run?

I'm currently working on a chart configuration using amCharts, where an eventListener is registered for the bullets. This event listener then triggers another function in my chart service. My goal is to activate a method in my component as soon as th ...

The call to react.cloneElement does not match any overloads

I'm encountering a typescript error in my React code when using React.cloneElement with the first parameter "children", and I am unsure of how to resolve it. As a newcomer to typescript, I believe that the type definitions in index.d.ts for cloneElem ...

What is the best way to organize objects based on their timestamps?

I am faced with the task of merging two arrays of objects into a single array based on their timestamps. One array contains exact second timestamps, while the other consists of hourly ranges. My goal is to incorporate the 'humidity' values from t ...

Develop a directive for transforming data

In my latest project, I am looking to develop a LoaderDirective that can fetch an observable, display a spinner while loading the data, and then switch to showing the actual data once loaded. I also want it to expose the loaded data using the 'as&apos ...

Encountering build errors while utilizing strict mode in tsconfig for Spring-Flo, JointJS, and CodeMirror

While running ng serve with strict mode enabled in the tsconfig.json, Spring-Flow dependencies are causing errors related to code-mirror and Model. https://i.sstatic.net/KUBWE.png Any suggestions on how to resolve this issue? ...

Warning: Typescript is unable to locate the specified module, which may result

When it comes to importing an Icon, the following code is what I am currently using: import Icon from "!svg-react-loader?name=Icon!../images/svg/item-thumbnail.svg" When working in Visual Studio Code 1.25.1, a warning from tslint appears: [ts] Cannot ...

Setting up Tarui app to access configuration data

I am looking to save a Tauri app's user configuration in an external file. The TypeScript front end accomplishes this by: import {appConfigDir} from "tauri-apps/api/path"; ... await fetch(`${await appConfigDir()}symbol-sets.json`) { ... ...

What is the process of inserting a sparkline chart into a Kendo Angular grid?

I am attempting to display a bullet chart in the first column of my grid. <kendo-grid-column> <ng-template kendoChartSeriesTooltipTemplate let-value="value"> <div> <kendo-sparkline [data]="bulletData" type="bullet" [ ...

Tips on saving a cookie using universal-cookie

I followed a solution on Stack Overflow to set a cookie in my React application. However, the cookie expires with the session. Is there a way I can make this cookie persist beyond the session so it remains even when the browser is closed and reopened? ex ...

When utilizing *NgIf, the button will be shown without the accompanying text being displayed

When trying to display either a confirm or cancel button based on a boolean set in my component.ts, I implemented the following code in my HTML: <mat-dialog-actions class="dialog-actions"> <button class="cancel-btn" ...

Resolving type error issues related to using refs in a React hook

I have implemented a custom hook called useFadeIn import { useRef, useEffect } from 'react'; export const useFadeIn = (delay = 0) => { const elementRef = useRef<HTMLElement>(null); useEffect(() => { if (!elementRef.current) ...

Using an AWS API Gateway, an HTTP client sends a request to access resources

I have a frontend application built with Angular and TypeScript where I need to make an HTTP request to an AWS API Gateway. The challenge is converting the existing JavaScript code into TypeScript and successfully sending the HTTP request. The AWS API gat ...

Dynamic Angular select options with ngFor causing cascading changes in subsequent selects

In my Angular 5 project, I am attempting to create a set of three <select> elements using *ngFor. I came across a helpful thread on Stack Overflow that provided some guidance (check it out here). However, I've encountered an issue where the sele ...

Filtering the data in the table was successful, but upon searching again, nothing was found. [Using Angular, Pagination, and

Having a table with pagination, I created a function that filters the object and displays the result in the table. The issue arises when I perform a new search. The data from the initial search gets removed and cannot be found in subsequent searches. Bel ...