Caleb Eby
Caleb Eby

Reputation: 386

How to create a mapped type for resolved promises in Typescript

I want to be able to have a mapped type that has resolved promise values.

This mapped type promisifies all the keys:

type PromisifiedKeys<T> = { [K in keyof T]: Promise<T[K]> }

I want a mapped type that un-promisifies/resolves them:

type PromisifiedKeys<T> = { [K in keyof T]: UnPromise<T[K]> }

I'm not sure how to do this.

Upvotes: 2

Views: 1147

Answers (3)

amarillion
amarillion

Reputation: 24937

Since TypeScript 4.5, you can also use the Awaited utility type.

Awaited is pretty much exactly the Unpromise template that you are looking for.

type UnpromisifiedKeys<T> = { [K in keyof T]: Awaited<T[K]> }

Upvotes: 1

Adam Mazzarella
Adam Mazzarella

Reputation: 763

As of Typescript 2.8 there are conditional types. From the handbook:

type Unpacked<T> =
    T extends (infer U)[] ? U :
    T extends (...args: any[]) => infer U ? U :
    T extends Promise<infer U> ? U :
    T;

Usage example:

const p: Promise<string> = getData();
type ReturnDataType = Unpacked<p>;

I was able to implement this as such:

interface AsyncSupplementalData {
    couponCodeData: Promise<string>;
}
interface ApplicationState {
    [K in keyof AsyncSupplementalData]: Unpacked< AsyncSupplementalData[K] >
}

Upvotes: 3

jcalz
jcalz

Reputation: 329598

You really want mapped conditional types, which don't currently exist in TypeScript.


There are workarounds. If you're willing to pollute the global declaration of Promise<T> with a phantom property, you can do this:

declare global {
  interface Promise<T> {
    "**unPromise**": T
  }
}
export type UnPromise<T extends Promise<any>> = T['**unPromise**']
export type UnPromisifiedKeys<T extends { [k: string]: Promise<any> }> = 
  {[K in keyof T]: UnPromise<T[K]> }

And you get:

type Promised = {
  foo: Promise<string>,
  bar: Promise<number>
}
type UnPromised = UnPromisifiedKeys<Promised>; 
// {foo: string, bar: number}

This is more or less what you asked for, but it's hacky.


Or, you could use inference from mapped types to have a function that takes an object of type PromisifiedKeys<T> and returns a T:

declare function unPromisifyKeys<T>(k: PromisifiedKeys<T>): T;

This represents as a function what you want to do with the types. If you have concrete things you want to pass to this function, it might be useful to you. (That is, if you were just looking for UnpromisifyKeys<T> as a way to represent the output of a function, then this solution will work as-is). If you really need to figure out UnpromisifyKeys<T> at the type level without a concrete T value, then you can jump through hoops to force the compiler to infer the right type without causing too much busy work at runtime:

const unpromised = true as false || unPromisifyKeys(null! as Promised);
type Unpromised = typeof unpromised; 
// {foo: string, bar: number}

This works but it is ugly. The only thing it has going for it over the first workaround is that the global definition of Promise<T> is unchanged.


Maybe one of those workarounds is good enough for you? Hope that helps; good luck!

Upvotes: 1

Related Questions