microsoft/TypeScript

Add a new `type Awaitable<T> = T | PromiseLike<T>`

remcohaszing opened this issue Β· 18 comments

Search Terms

  • awaitable

Suggestion

Add an awaitable type for values that will be awaited.

type Awaitable<T> = T | PromiseLike<T>;

This is based on my question and a comment on StackOverflow

Use Cases

Two use cases come in mind immediately:

  1. A function accepts a callback that may either return a value synchronously, or may return a promise value. This will then probably be awaited.
  2. This is more of a specific version of 1, but this would be the return type of Promise.then() / Promise.catch / Promise.finally callbacks.

Also, this type could replace all 1334 occurrences that come up when running git grep '| Promise' in the current TypeScript code base.

Examples

Callback example:

async function logAnswer(getAnswer: () => Awaitable<number>): Promise<void> {
  const answer = await getAnswer();
  console.log(answer);
}

logAnswer(() => 42);
logAnswer(() => Promise.resolve(42));

Promise example:

Promise.resolve('Hello, world!').then(
  // This type annotation is silly. This is really just to show promise callbacks should accept `Awaitable<T>`.
  (hello): Awaitable<string> => {
    console.log(hello);
    return hello;
  },
);

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.

At first I didn't see the utility of this since everything in JavaScript is awaitable, but I just realized it's good for type inference:

type Awaitable<T> = T | PromiseLike<T>;

declare function foo<T>(callback: () => T): Promise<T>;
declare function bar<T>(callback: () => Awaitable<T>): Promise<T>;

const fun = () => Promise.resolve(812);
let food = foo(fun);  // Promise<Promise<number>>, which is not actually a thing.
let bard = bar(fun);  // Promise<number> - yes!

I think Promisable would be a better name than Awaitable.

Since any value is awaitable in TS this name seems too generic to me, Promisable on the other hand says to me that the value could be actually a Promise (technically a PromiseLike, but PromiseLikeable or PromiseableLike sound awful to me).

I think Promisable and Awaitable are both fair, but I notice that ForAwaitable is another type under discussion and recommend that these two types are aligned regardless of the direction we go.

#36153

Some feedback from future self: I have been using Promisable a lot. I really hope this will be a builtin type.

This was added in #45350 and will be part of TypeScript 4.5.

I misinterpreted it. It’s not the same.

I've been using this exact definition in many of my projects for years. It will be great if this is built in.

Been using the same Promisable impl from #31394 (comment) since summertime last year. Hoping the same (it becoming a built-in)! It cuts down on a lot of code and improves the developer experience IMO.

Is this relevant anymore given #51841 (comment)?

I like Awaitable more than Promisable. The whole point of the type is that you can use await on the type to "unwrap" its generic type. It is a corollary to the Awaited type. Promisable to me conveys a semantic like PromiseLike which is not right.

Another name idea is PromiseOr<T>. The Dart language has the same concept but called FutureOr.

This issue predates that comment and its references. I still think a builtin Awaitable type would be useful, but the TypeScript team is free to close this issue if they feel otherwise.

I know this is closed, but I want to highlight one important argument for this type that hasn't been raised.

The Awaitable type allows you to define function type that is "optionally async". Optionally async functions is a very widely useful pattern.

type NumberCallback = () => Awaitable<number>;

// these both work!
const x: NumberCallback = () => 42;
const y: NumberCallback = async () => 42;

The merits of an individual utility type aren't really relevant since our position is "No new utility types, at all". If the type is useful for you, you should definitely write it in your project with the definition you'd like it to have.

btoo commented

@RyanCavanaugh thanks for the update - just for my own learning, is there a decision record rationalizing the "No new utility types, at all" position I can read up on? nevermind, I found it

@btoo Not only that there's no such a record, they literally just added a new NoInfer utility type in v5.4 so I have no idea what he's talking about.

@MuTsunTsai i wouldn't call NoInfer a utility type as you cannot implement it yourself in library code.

@ritschwumm Your opinion is irrelevant. They literally called it "The NoInfer Utility Type".