Description
Originally posted by @parzhitsky in #33595 (comment)
Okay, so, if I understand correctly, you are saying that this is wrong (or weird at least):
async function doStuff(): string {
… and so is this:
async function doStuff(): string | Promise<string> {
But there are situations when you can’t really control the structure of the type union, since you don’t create it, like here:
import type ApiResponse from "some-library/types";
import { onlyAcceptsApiResponse } from "some-library";
declare function doStuff(): ApiResponse<string>;
onlyAcceptsApiResponse(doStuff());
… and if ApiResponse<string>
in the example above turns out to resolve to string | Promise<string>
, you cannot use async
keyword anymore, and forced to fallback to .then
s and .catch
es if working with promises.
But you also cannot do this:
type UnwrapPromise<Value> =
Value extends PromiseLike<infer Bare> ? UnwrapPromise<Bare> : Value;
type PromiseOnly<Value> =
Promise<UnwrapPromise<Value>>;
async function doStuff(): PromiseOnly<ApiResponse<string>> {
… because, while it works, it also requires for you to a) know the implementation of ApiResponse
(which you shouldn’t know), and b) navigate your way through unreasonable TypeScript compiler errors that don’t bring any value.
Is this correct?
If it is, then I agree with the proposal. Not only that, I would actually go a bit further, and propose to remove the distinction between “bare” and promise-wrapped values in return signatures of async
functions altogether. Meaning that these signatures:
async function doStuff(): string {
async function doStuff(): Promise<string> {
… should be considered indistinguishably identical.