question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Allow async functions to return union type T | Promise<T>

See original GitHub issue

Search Terms

“the return type of an async function or method must be the global Promise type” “typescript promise union type”

Suggestion

You can explicitly declare a function’s return type as a union that includes a Promise<T>. While this works when manually managing promises, it results in a compiler error when using the async/await syntax.

For example, a function can have a return type T | Promise<T>. As the developer of an abstraction layer, this allows you to have an abstract return type that can be handled in a typesafe way but doesn’t dictate implementation to consumers.

This improves developer ergonomics for consumers of a library without reducing the safety of the type system by allowing developers to change implementation as the system evolves while still meeting the requirements of the abstraction layer.

This only works, currently, if the developer explicitly manages the promises. A developer may start with something like this:

type ActionResponse<T> = T | Promise<T>;

function getCurrentUsername(): ActionResponse<string> {
  return 'Constant Username';
}

async function logResponse<T>(response: ActionResponse<T>): Promise<void> {
  const responseValue = await response;
  console.log(responseValue);
}

logResponse(getCurrentUsername());
// Constant Username

Then, if the consumer of logResponse switches to a promise based method, there’s no need to change the explicit return type:

function getCurrentUsername(): ActionResponse<string> {
  // return 'Constant Username';
  return Promise.resolve('Username from Database');
}

// Username from Database

However, if the consumer of logResponse prefers to use async/await instead of manually managing promises, this no longer works, yielding a compiler error instead:

The return type of an async function or method must be the global Promise<T> type.

One workaround is to always return promises even when dealing non-async code:

async function getCurrentUsername(): Promise<string> {
  return 'Constant Username';
  // return Promise.resolve('Username from Database');
}

Another workaround is to use an implicit return type:

async function getCurrentUsername() {
  return Promise.resolve('Username from Database');
}

These do get around the issue for sure, but they impose restrictions on consumers of the abstraction layer causing it to leak into implementation.

It seems valuable for the behavior to be consistent between using async/await and using Promise directly.

Use Cases

This feature would be useful for developers who are building abstraction layers and would like to provide an abstract return type that could include promises. Some likely examples are middlewares, IoC containers, ORMs, etc.

In my particular case, it’s with inversify-express-utils where the action invoked can be either async or not and the resulting behavior doesn’t change.

Examples

// this type
type ActionResponse<T> = T | Promise<T>;

// supports this function
function getCurrentUsername(): ActionResponse<string> {
  return 'Constant Username';
}

// as it evolves over time into this function
async function getCurrentUsername(): ActionResponse<string> {
  return Promise.resolve('Username from Database');
}

// and is handled transparently by functions like this
async function logResponse<T>(response: ActionResponse<T>): Promise<void> {
  const responseValue = await response;
  console.log(responseValue);
}

logResponse(getCurrentUsername());

Checklist

My suggestion meets these guidelines:

  • This wouldn’t be a breaking change in existing TypeScript/JavaScript code
  • This wouldn’t change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript’s Design Goals.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:17
  • Comments:15 (5 by maintainers)

github_iconTop GitHub Comments

8reactions
yortuscommented, Sep 26, 2019

Or more briefly, TypeScript has a structural type system and pretty consistently applies it, except with async function return types, where it does a nominal, not structural, check.

Usually when you specify parameter and return type annotations on a function in TypeScript, the caller can pass in anything that is assignment-compatible with the parameter types, and the function can return anything that is assignment compatible with the return type. Likewise with variable assignments, property assignments, etc etc. Async function return types are a rare exception to this rule.

Even generator functions, which just like async functions always return a known system type, can be annotated with assignment-compatible return types which are checked structurally.

IMO there is an inconsistency here. The consequences are arguably not huge, but have been reported in a number of issues over the years so do affect at least some users. I can’t really see the downsides of removing this inconsistency.

6reactions
RyanCavanaughcommented, Sep 25, 2019

I don’t understand the need for this. Why does the second getCurrentUsername have the async modifier? It works perfectly without it.

You only need the async modifier if you use the await operator in the function body, in which case the function never returns a bare T (even an immediate return triggers an event loop tick, per ES spec) and it makes more sense to just write Promise<string> as the return type.

Read more comments on GitHub >

github_iconTop Results From Across the Web

In TypeScript, why can't an async function return a union of ...
Type against Promise<string> and let async handle the wrapping into promise for you or actually return other promises in the real async ......
Read more >
TypeScript with Promises, Async/Await, and Generator Functions
Well, we just add a union type in the type declaration of the Promise and that way both the return type of the...
Read more >
async function - JavaScript - MDN Web Docs - Mozilla
Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly...
Read more >
Declare a function with a Promise return type in TypeScript
If the return type of the function is not set, TypeScript will infer it. ... Unwrap promise type if necessary // 👇️ type...
Read more >
How To Use Generics in TypeScript - DigitalOcean
<T> can be read as a generic of type T . In this case, T will ... Note: As your function is async...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found