ucag
ucag

Reputation: 477

How to dynamic assign type in typescript?

I met a wrapper function type problem.

I want turn a function with callbacks into a promise function. almost of those functions are like this:

func({
  success:....,
  fail:....,
  complete:...
})

and I wrote a wrapper function:

function toAsync<
        OptType
        SuccesResType,
        FailResType>(fn:(opt:OptType)=>any):(opt:OptType)=>Promise<SuccesResType>{
  
  return async (opt:OptType):Promise<SuccesResType>=>{
    return new Promise<SuccesResType>((resolve,reject)=>{
      fn({
        ...(opt || {}),
        success(res:SuccesResType){
          resolve(res)
        },
        fail(res:FailResType){
          reject(res)
        }
      } as unknown as OptType)
    })
  }
}

and there is the problem, basically the SuccessResType is parameter type of opt.success, FailResType is the parameter type of opt.fail.

how can I define my toAsync wrapper to function to get the right type hint when use it like this:

const fn = toAsync(fn)

instead of some duplicated works like

type fnOptType = Parameters<typeof fn>[0]
type fnSuccessRestype = Parameters<fnOptType.SuccessCallback>[0]
type fnFailResType = Parameters<fnOptType.FailCallback>[0]
type fn = toAsync<fnOptType,fnSuccessRestype,fnFailResType>(fn)

Right now, if I use toAsync like const fn = toAsync(fn), I can't get any hint option until I manually write the generic type.

Update: for fn, its option type can change.like:

function request(opt:{
    fail?:(res:RequestFailResType)=>any;
    success?:(res:RequestSuccessResType)=>any;
    complete?:(res:RequestCompleteResType)=>any;
    url:string
}):any{
    // do things here
}

function locationQuery(opt:{
    fail?:(res:QueryFailResType)=>any;
    success?:(res:QuerySuccessResType)=>any;
    complete?:(res:QueryCompleteResType)=>any;
    lat:number,
    log:number
}):any{
    // do things here
}

toAsync needs to handle those type changes. It seems a big compatible requirement.

I have hundreds of functions like this, therefore I want a more convenient way to do this backwards type support.

Upvotes: 0

Views: 169

Answers (1)

Shivam Singla
Shivam Singla

Reputation: 2201

The solution is to make OptType generic. Like this-

interface OptType <T1, T2> {
  complete: () => void
  success: (successResult: T1) => void
  fail: (failResult: T2) => void
}

Then you can pass the argument to toAsync with generics involved-

fn: (opt: OptType<SuccessResType, FailResType>) => any

This time the types for SuccessResType and FailResType will be inferred automatically. The whole solution will be-

declare function myFunc(opts: MyFuncOptType): void

interface OptType <T1, T2, T3> {
  complete?: (res: T3) => void
  success?: (successResult: T1) => void
  fail?: (failResult: T2) => void
}

type SuccessResultType<O extends OptType<any, any, any>> = O extends OptType<infer T1, any, any>
  ? T1
  : never

type FailResultType<O extends OptType<any, any, any>> = O extends OptType<any, infer T2, any>
  ? T2
  : never

declare interface MyFuncOptType extends OptType<number, boolean, any> {
  someProp: string
  someOtherProp: boolean[]
  someFunc: (p: number) => string
}

function toAsync<O extends OptType<any, any, any>>(fn: (opt: O) => any) {
  return async (opt: Omit<O, 'success' | 'fail' | 'complete'>) => {
    return new Promise<SuccessResultType<O>>((resolve, reject) => {
      fn({
        ...(opt || {}),
        success(res: SuccessResultType<O>){
          resolve(res)
        },
        fail(res: FailResultType<O>){
          reject(res)
        }
      } as unknown as O)
    })
  }
}

const promisedMyFunc = toAsync(myFunc)
promisedMyFunc({
  someProp: 'va',
  someOtherProp: [false],
  someFunc: (p) => p.toString()
}).then((res) => {
  console.log(res) // res is number
})

function request(opt:{
    fail?: (res: string) => any;
    success?: (res: boolean) => any;
    complete?: () => any;
    url: string
}): any{
    // do things here
}

const promisedRequest = toAsync(request)
promisedRequest({
  url: '',
}).then((res) => { // res is Blob
  console.log(res)
})

function locationQuery(opt:{
    fail?:(res: 'type1') => any;
    success?:(res: 'type2') => any;
    complete?:(res: 'type3') => any;
    lat:number,
    log:number
}): any {
    // do things here
}


const promisedLocationQuery = toAsync(locationQuery)
promisedLocationQuery({
  lat: 34,
  log: 34
}).then((res) => { // res is "type2"
  console.log(res)
})

[Playground]

Please note that I have removed unnecessary types. And rename T1 and T2 to your convenience.

Upvotes: 1

Related Questions