Reputation: 5463
This is a continuation of a question about resolving return types. I'm so close to what I'm looking to do (which is to have a common function to build model instances based on validated data) and it works fine in JS, but I can't figure out the final part for the TS typing.
The key is the return for my Init
type - specifically the Builder<T[K]>
part. I understand why that results in Builder<(id: number) => Promise<Test>>
as the returned type, but I can't for the life of me figure out how to tell TS that in fact it is just Test
being returned, not the function that returns a Promise of Test
.
The following can be copy / pasted into VSCode (or whatever) to show the issue. The ter1
variable should be an instance of Test
, and when compiled to JS it is, but TS doesn't see that.
export interface Builder<T> {
(values: any, db: any): Promise<T>
}
export interface Wrapper {
<T, TId>(
construct: (id: TId, db: any | null) => Promise<T>,
idSrc: string | string[],
errorMsg: string
): Builder<T>
}
let builder: Wrapper = function (construct, idSrc, errorMsg ) {
// A function that can be used to construct the instance
return async function(values: any, db: any | null) {
let id = null;
let inst = await construct(id, db);
if (!inst) {
throw new Error(errorMsg);
}
return inst;
};
}
class Test {
is1 = true;
static async fromId( id: number ) {
var inst = new Test();
// Some async action (e.g. a db read)
return inst;
}
}
class Test2 {
is2 = true;
static async fromId( id: number ) {
var inst = new Test2();
// Some async action (e.g. a db read)
return inst;
}
}
type Config<T extends {}> = {
inputs?: {[index:string]: any},
inst?: T;
};
type Init = <T extends {[key:string]: any}>(
db: any,
config: Config<T>
) => Promise<{[K in keyof T]: Builder<T[K]>}>; // ???
let init: Init = async function ( db, config ) {
let ret: any = {};
if ( config.inst ) {
for (let [key, value] of Object.entries(config.inst)) {
let res = await value( {}, {} );
ret[ key ] = res;
}
}
return ret;
}
async function allan () {
let { ter1, ter2 } = await init( null, {
inst: {
ter1: Test.fromId,
ter2: Test2.fromId
}
} );
console.log( ter1.is1, ter1.is2 ); // should be `true undefined`
// Test `builder` typing
var t1Wrapper = builder( Test.fromId, 'id', 'test');
var t2Wrapper = builder( Test2.fromId, 'id', 'test');
var t1 = await t1Wrapper({}, {});
var t2 = await t2Wrapper({}, {});
t1.is1;
t2.is2;
}
allan();
Thank you!
Upvotes: 6
Views: 8287
Reputation: 13681
TypeScript 4.5 added the Awaited
type.
// A = string
type A = Awaited<Promise<string>>;
// B = number
type B = Awaited<Promise<Promise<number>>>;
// C = boolean | number
type C = Awaited<boolean | Promise<number>>;
Upvotes: 6
Reputation: 25287
Just use AsyncReturnType
from type-fest
!
type-fest
has something identical to @jcalz's Unpromise
- it's simply named PromiseValue
instead.
AsyncReturnType<FOO>
is basically just PromiseValue<ReturnType<FOO>>
So, go ahead:
npm install type-fest --save-dev
yarn add type-fest --dev
And use like so:
type ServerData = AsyncReturnType<typeof getServerData>
Upvotes: 0
Reputation: 328252
It looks like the types you want are something like these:
type Unpromise<T extends Promise<any>> = T extends Promise<infer U> ? U : never;
type Init = <T extends { [key: string]: (...args: any[]) => Promise<any> }>(
db: any,
config: Config<T>
) => Promise<{ [K in keyof T]: Unpromise<ReturnType<T[K]>> }>;
But I haven't spent the time going through all your code to see if it makes sense or if you've implemented it correctly (is this really a minimum example?), so please forgive me if you run into some other problem.
Explanation: the way you call init()
implies that T
should be an object whose properties are functions that return a promise of the kind you care about. (I'm not sure if the types and numbers of arguments to those functions are important). So for each property in T
you want to extract its return type and then extract the promise type.
Unpromise<T>
uses type inference in conditional types to pull out the type of the promised value; so Unpromise<Promise<XXX>>
should be XXX
.
ReturnType<T>
is a type from the standard TypeScript library that does a similar thing to get the return type of a function; so ReturnType<()=>XXX>
should be XXX
.
Hope that helps. Good luck!
Upvotes: 15
Reputation: 309
The unit type is a function that return a Promise<{[K in keyof T]: Builder<T[K]>}>
.
Instead it should just return a Promise<{[K in keyof T]: T[K]}>;
.
Also in the init function you are calling
let res = await value( {}, {} );
But value is the fromId function of Test, that accept only one number parameter.
Upvotes: 0