0
votes

I have a function that returns a promise that will be resolved using reflect.

(Promise with, resolve and reject function is queued and then execute)

The input of the function is a method from an object with params as you can see in SingleTask type.

Because I don't return anything inside a promise typescript give the Promise<any> as a result.

But in practice, the result is Promise<Returntype <FIRST PARAM OF SINGLE TASK TYPE>> - Some function from an object

How can I declare the correct return type

type AnyClass = { new (...arg0: any): any }

type SingleTask<V extends AnyClass> = {
  [P in keyof InstanceType<V>]-?: InstanceType<V>[P] extends (...a: any) => any
    ? [P, Parameters<InstanceType<V>[P]>]
    : never
}[keyof InstanceType<V>]

  addTaskToQueue(task: SingleTask<T>): Promise<ReturnType<**What i should put here ?** >> {
    return new Promise((resolve, reject) => {
      const instance = this.instances.pop()
      if (instance) {
        this.runTasksInQueue(instance, { task, resolve, reject })
      } else {
        this.queue.push({ task, resolve, reject })
      }
    })
  }

Typescript playground with full code

1
The question "how I can help typescript to understand that I will return the promise with a return type of the return type of function that is inside the first element of the array with is given in input?" is very confusing. Could you rephrase that to be clearer? Perhaps with multiple shorter sentences instead of one long one? Also, your example code doesn't seem to be a minimal reproducible example; could you edit the code so that anyone can put it into a standalone IDE like The TypeScript Playground and see the issue for themselves? Good luck!jcalz
I rephrase it, I think now is more understandable. Sorry but English isn't my first languageDamian Grzanka

1 Answers

1
votes

I'm going to assume you want this sort of behavior:

type SomeCtor = new () => { a(x: string, y: number): boolean, b(z: number): string }
declare const q: Queue<SomeCtor>;
const a = q.addTaskToQueue(["a", ["", 1]]); // Promise<boolean>;
const b = q.addTaskToQueue(["b", [1]]); // Promise<string>;

I'm also only going to worry about the type signature of addTaskToQueue() and not about your implementation and how the implementation interacts with the signature. From the type system's point of view, with your types as you've defined, I'd do something like this:

declare class Queue<T extends AnyClass> {
  addTaskToQueue<S extends SingleTask<T>>(task: S): Promise<ReturnType<InstanceType<T>[S[0]]>>;
}

The addTaskToQueue() method must be generic, or the best you will do is get another union as the return type, corresponding to the entire SingleTask<T> union. You want the return type to depend on which member of the SingleTask<T> union was passed in. Once we have a type parameter S corresponding to some member of SingleTask<T>, we can express the return type as

Promise<ReturnType<InstanceType<T>[S[0]]>>

Note that S[0] will be the key of InstanceType<T> passed in, so we are looking up that property. You can verify that gives you the behavior I think you want.

So that's the answer to the question as asked.


As an aside, it's not clear why your generic T is a constructor type that you invariably process with InstanceType<T>. The only thing it looks like you're doing with objects of type T is calling new this.object(), which specifically assumes that the constructor takes zero arguments. If so, I'd suggest changing T to be the instance type itself, and refer to the constructor as type new()=>T:

type SingleTask<T> = {
  [P in keyof T]-?: T[P] extends (...a: any) => any
  ? [P, Parameters<T[P]>]
  : never
}[keyof T]

type Task<T> = {
  task: SingleTask<T>
  resolve: (value?: unknown) => void
  reject: (reason: any) => void
}

declare class Queue<T> {
  instances: T[];
  queue: Task<T>[];
  object: new () => T;
  addTaskToQueue<S extends SingleTask<T>>(task: S): Promise<ReturnType<T[S[0]]>>;
}

type SomeType = { a(x: string, y: number): boolean, b(z: number): string }
declare const q: Queue<SomeType>;
const a = q.addTaskToQueue(["a", ["", 1]]); // Promise<boolean>;
const b = q.addTaskToQueue(["b", [1]]); // Promise<string>;

That's significantly simpler.

Playground link to code