1

How can I type this function in relation to its input fn?

function makeAsync(fn) {
  return async (...args) => fn(...args);
}

It returns a function identical to its input, but instead of returning Type it would return Promise<Type>

Usage example:

const a = () => 1; // Type () => number;
const b = makeAsync(a); // Type () => Promise<number>;
const c = makeAsync(b); // Type () => Promise<number>; // ✅, not Promise<Promise<number>>

This works, but it's a little verbose

// Unwraps a Promise<T> value into just T, so we never get Promise<Promise<T>>
type Unpromise<MaybePromise> = MaybePromise extends Promise<infer Type> ? Type : MaybePromise;

// Like ReturnType, except it returns the unwrapped promise also for async functions
type AsyncReturnType<T extends (...args: any[]) => any> = Unpromise<ReturnType<T>>

// For a `() => T` function it returns its async equivalent `() => Promise<T>`
type PromisedFunction<T extends (...args: any[]) => any> =
    (...args: Parameters<T>) => Promise<AsyncReturnType<T>>;
function makeAsync<T extends (...args: any[]) => any>(fn: T): PromisedFunction<T> {
  return async (...args) => fn(...args);
}

TypeScript Playground link

Are there any better/shorter ways to achieve this?

3
  • Would overloading the function for … => T and … => Promise<T> work? Commented Dec 1, 2019 at 13:49
  • The types of my real function are a little complex already, I think adding an overload would not help... but perhaps it's worth a try! Commented Dec 1, 2019 at 16:53
  • I'll link to the bug that causes the nonsensical Promise<Promise<T>> so in the future a solution could be simpler: github.com/microsoft/TypeScript/issues/27711 Commented Dec 1, 2019 at 16:56

1 Answer 1

1

You can shorten up the types a bit by defining separate type parameters A,R for the function parameters and return type, so they are inferred automatically. Then it is easy to just wrap a Promise around R in makeAsync2 (sample):

declare function makeAsync2<A extends any[], R>(fn: (...args: A) => R): (...args: A) => Promise<R>

const c = (arg1: number, arg2: string[]) => 1; // (arg1: number, arg2: string) => number
const d = makeAsync2(c); // (arg1: number, arg2: string[]) => Promise<number>
const cResult = c(3, ["s"]) // number
const dResult = d(3, ["s"]) // Promise<number>

Edit:

If the input function fn potentially can itself return a promise, we can set its return type to the union R | Promise<R>(sample):

function makeAsync2<A extends any[], R>(fn: (...args: A) => R | Promise<R>): (...args: A) => Promise<R> {
  return async (...args) => fn(...args);
}

const e = (arg1: string) => Promise.resolve(3) // (arg1: string) => Promise<number>
const f = makeAsync2(e); // (arg1: string) => Promise<number>
const eResult = e("foo") // Promise<number>
const fResult = f("foo") // Promise<number>
Sign up to request clarification or add additional context in comments.

9 Comments

This is not what the OP is looking for. When you call makeAsync2 on an already-asynchronous function, you get back a Promise<Promise<R>> type, correct would be only Promise<R>.
Not sure, if async returning input functions are part of the requirement because of sentence "a function identical to its input, but instead of returning Type it would return Promise<Type>" - that would indicate the opposite. But you are probably right, Unpromise would otherwise not make so much sense. Thanks, gonna update answer.
That looks good! I was using Unpromise exactly to avoid the Promise<Promise<T>> you'd get with Promise<ReturnType< async function here >>
makeAsync2 works as I expect, but I can't seem to apply it to a real function: typescriptlang.org/play/#code/…
@fregante in this case it would be perfectly fine to cast the return type like here. It seems, your case can get even easier: TS automatically flattens the Promise type with the async keyword, so no need for FlattenPromise here (updated answer).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.