What prevents TypeScript from allowing an async function to return a combination of type T or Promise<T>?

During the development of my API in typescript, I encountered a situation where some controller actions can be synchronous while others cannot. To address this issue, I decided to specify a response type as follows:

type ActionResult =IHttpActionResult | Promise<IHttpActionResult>;

As I proceed with building these actions, when they shift towards being promise-based, I simply add the 'async' keyword and move on.

However, I faced an obstacle when typescript raised an error stating that "the return type of an async function or method must be the global Promise type."

I wondered why an async function couldn't return a union of T | Promise<T>?

For instance:

type StringPromise = Promise<string>;

// The following two functions work as expected
async function getHello(): Promise<string> {
    return 'hello';
}

async function getGoodbye(): StringPromise {
    return 'goodbye';
}

type StringyThingy = string | Promise<string>;

// These next two functions work as intended
function getHoorah(): StringyThingy {
    return 'hoorah!';
}

function getWahoo(): StringyThingy {
  return new Promise(resolve => resolve('wahoo'));
}

// However, this function triggers the error:
// "the return type of an async function or method must be the global Promise type."
async function getSadface(): StringyThingy {
  return ':(';
}    

Below are some sample outputs for the provided code:

getHello().then(console.log);
getGoodbye().then(console.log);
console.log(getHoorah());

// It is likely that the library I am using employs typeguards for this purpose
const wahoo = getWahoo();
if (typeof(wahoo) === 'string') {
  console.log(wahoo);
} else {
  wahoo.then(console.log);
}

Answer №1

Utilizing the async notation is essentially a convenient way to express: "This function will always yield a promise."

Even when explicitly declared as:

const foo = async() => 3;

It essentially equates to (albeit more stringent):

const foo = () => new Promise(resolve => resolve(3));

or alternatively:

const foo = () => Promise.resolve(3);

All of these illustrations generate a Promise.

The primary contrast lies in the fact that regular functions have the capacity to return both a Promise and other data types, but with the utilization of async, the outcome will unfailingly be a promise.

Even if a promise gets resolved immediately, an async function is inherently designed to invariably return a promise without exception.

Subsequently, you must await it or use then() on it.

This concept is also expounded upon in Mozilla's JavaScript reference concerning the async keyword:

The async function declaration defines an asynchronous function, which returns an AsyncFunction object. An asynchronous function operates via the event loop, leveraging an implicit Promise for results retrieval. However, the syntax and structure of coding with async functions bear a closer resemblance to standard synchronous functions.

With regard to the return type:

A Promise, either resolved with the value returned by the async function, or rejected due to an unhandled exception originating from within the async function.


In light of this, I suggest defaulting to making your API inherently async. The external environment need not concern itself if specific actions are synchronous. In such instances, you can promptly resolve the promise. Dispensing with the need for the

type StringyThingy = string | Promise<string>;

Embrace Promise<string> as the type and allow async to streamline the conversion into a promise for you, or alternatively, return actual promises in genuine async scenarios. This approach eliminates the necessity for differentiating between synchronous and asynchronous branches.


If the union type is truly indispensable (though strongly discouraged), then relinquishing the async keyword becomes imperative.

You can define standard functions that may return either type:

const foo = (x:number): Promise<number>|number => {
    if(x >=0) {
         return new Promise(resolve => resolve(x));
    } else {
         return x;
    }
}

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

Steps to insert a personalized attribute into a TypeScript interface

UPDATED EXPLANATION: I'm fairly new to TypeScript, so please bear with me if this question seems basic. I'm working with an existing library (ngx-logger) that I don't want to or can't modify. My goal is to create a service that generat ...

Contrasting covariant and contravariant positions within Typescript

I'm currently diving into the examples provided in the Typescript advanced types handbook to broaden my understanding. According to the explanation: The next example showcases how having multiple potential values for the same type variable in co-var ...

What is the best way to manage the 'content' attribute in TSX?

I'm currently developing an application that utilizes schema.org. In the code snippet below, you can see how I've implemented it: <span itemProp="priceCurrency" content="EUR">€</span> According to schema.org do ...

Adding to an existing array in SQLite by updating a column using Sequelize

My code includes a model definition for saving product data using Sequelize: This is how the Product model looks: import {Optional, Model, Sequelize, DataTypes } from 'sequelize'; /*This is the Product model used to save the data about products* ...

Challenges arise when trying to access environment variables using react-native-dotenv in React

I am currently working on two separate projects, one being an app and the other a webapp. The app project is already set up with react-native-dotenv and is functioning as expected. However, when I attempt to use the same code for the webapp, I encounter an ...

In my coding project using Angular and Typescript, I am currently faced with the task of searching for a particular value within

I am facing an issue where I need to locate a value within an array of arrays, but the .find method is returning undefined. import { Component, OnInit } from '@angular/core'; import * as XLSX from 'xlsx'; import { ExcelSheetsService } f ...

What is the best way to anticipate a formal announcement?

Hey there! I'm trying to set options for my Datatable and add a new field in my objects, but I need to await the completion of these dtOptions. How can I achieve this in the ngOnInit lifecycle hook? export class MyDashboardComponent implements OnInit ...

The constant issue persists as the test continues to fail despite the component being unmounted from the

import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { act } from 'react' import Notifications, { defaultNotificationTime, defaultOpacity, queuedNotificationTime, fa ...

Is it possible to use TypeScript or Angular to disable or remove arrow key navigation from a PrimeNG Table programmatically?

Is there a way to programmatically prevent left and right arrow key navigation in a PrimeNG Table with cell editing, without the need to modify the Table component source code? You can check out an example here: Angular Primeng Tableedit Demo code. I mana ...

What is the best way to implement a switch case for the value of a property within an object in a TypeScript file?

The object I'm dealing with looks like this: {a: auth?.type === '1' || auth?.type === '2' || auth?.type === '3' ? { reason: // I need to implement a switch case here : un ...

Tips for exporting and reusing third-party types in TypeScript

I am facing a challenge with my package, which relies on a 3rd party package API for most of its functions. How can I export the types from the 3rd party package API in my own package? For instance: React uses @types/react to define its types Let's ...

I am experiencing issues with my HTML select list not functioning properly when utilizing a POST service

When using Angularjs to automatically populate a list with *ngFor and then implementing a POST service, the list stops functioning properly and only displays the default option. <select id="descripSel" (change)="selectDescrip()" > <option >S ...

Guide on setting up and configuring the seeder in MikroORM

Hey there, I recently tried to execute seeders in MikroORM and encountered a problem. I followed all the steps outlined here: . In the MikroORM route folder (alongside mikro-orm.config.ts), I created a seeders directory. I updated mikro-orm.ts with the fo ...

Exploring the power of Angular 10 components

Angular version 10 has left me feeling bewildered. Let's explore a few scenarios: Scenario 1: If I create AComponent.ts/html/css without an A.module.ts, should I declare and export it in app.module.ts? Can another module 'B' use the 'A ...

Utilize the synchronization feature of ES6 Promises in Jasmine with the then/catch method

I have an angular controller that needs to be tested. This controller utilizes a service to fetch data from a server, and the service returns ES6 Promises. function MyController($scope, MyService) { $scope.doSomething = function () { MyService.foo() ...

I seem to be failing at properly executing Promises... What crucial element am I overlooking in this process?

Within my project, there exists a file named token.ts which contains a function that exports functionality: import * as jwt from 'jsonwebtoken'; import { db, dbUserLevel } from '../util/db'; export function genToken(username, passwor ...

Exploring Typescript within React: Creating a property on the current instance

Within my non-TypeScript React component, I previously implemented: componentWillMount() { this.delayedSearch = _.debounce((val) => { this.onQuerySearch(val); }, 1000); } This was for debouncing user input on an input field. The corres ...

Why am I receiving a peculiar type error with @types/jsonwebtoken version 7.2.1?

My current setup includes node v6.10.3, typescript v2.3.4, and jsonwebtoken v7.4.1. Everything was running smoothly until I decided to upgrade from @types/jsonwebtoken v7.2.0 to @types/jsonwebtoken v7.2.1. However, after this update, an error started poppi ...

Ensure that parameters are validated correctly in the Next.JS application router using the searchParams method

When building the page, I need to properly validate params in the Next.JS app router using searchParams. My goal is to show a main image (coverImage) for each photo on the /gallery page. When a photo is clicked, I want to display more photos of the same k ...

The parameter type SetStateAction<MemberEntityVM[]> cannot be assigned the argument type Promise<MemberEntityVM[]> in this context

I am looking to display a filtered list of GitHub members based on their organization (e.g., Microsoft employees). Implementing React + TS for this purpose, I have defined an API Model which represents the structure of the JSON data from the GitHub API: ex ...