Я попытаюсь ответить на это, несмотря на то, что не совсем уверен, что вы делаете. Для начала давайте сделаем T
универсальный тип, возвращаемый переданным диспетчером, что-то вроде Promise<any>
или Observable<any>
или ReadableStream<any>
:
class Action<T> {
dispatcher: (x: any) => T;
constructor(dist: (x: any) => T) {
this.dispatcher = dist
}
f = (s: string) => this.dispatcher('hello' + s);
}
const promiseDispatcher = (x: any) => Promise.resolve(x)
const a = new Action(promiseDispatcher);
a.f("random string").then((s: string) => {
// ...
}); // works
Теперь это работает, и тип f
для Action<T>
подразумевает тип (s: string) => T
, а поскольку a
является Action<Promise<any>>
, то a.f(s)
является Promise<any>
. Обратите внимание, что несмотря на то, что f
имеет T
в своем типе возвращаемого значения, f
сам по себе не является тем, что вы считаете универсальной функцией. Универсальный параметр для класса , и как только у вас есть конкретный экземпляр класса, его функция f
также является конкретным типом функции.
Одна из проблем здесь заключается в том, что вы, вероятно, не хотите, чтобы a.f(s)
был Promise<any>
, а скорее увидел бы Promise<string>
. Оказывается, поскольку TypeScript в настоящее время не очень поддерживает типы с более высоким родом , нет общего способа сказать, чтобы сделать T
чем-то вроде Promise
или Observable
или ReadableStream
, которые сами являются общими типами. С условными типами вы можете выбрать несколько жестко закодированных типов, таких как Promise<any>
, Observable<any>
и ReadableStream<any>
и преобразовать их в Promise<string>
, Observable<string>
и ReadableStream<string>
соответственно:
type AppliedToString<T> =
T extends Promise<any> ? Promise<string> :
T extends Observable<any> ? Observable<string> :
T extends ReadableStream<any> ? ReadableStream<string> :
T;
Теперь, если мы ограничим T
этими жестко закодированными типами и воспользуемся функцией типа выше, мы должны получить что-то похожее на типирование, которое вы хотите для f
:
// constrain T
class Action<T extends Promise<any> | Observable<any> | ReadableStream<any>> {
dispatcher: (x: any) => T;
constructor(dist: (x: any) => T) {
this.dispatcher = dist
}
// assert return type of f as AppliedToString<T>
f = (s: string) => this.dispatcher('hello' + s) as AppliedToString<T>;
}
const promiseDispatcher = (x: any) => Promise.resolve(x);
const a = new Action(promiseDispatcher);
a.f("random string").then((s: string) => {
// ...
}); // works
Теперь, если вы проверите тип возврата a.f
, вы увидите, что это Promise<string>
.
Недостатком такого способа является то, что вам нужно поддерживать жестко закодированный список, и он также не особенно безопасен для типов, поскольку он позволит вам передать диспетчер, который возвращает Promise<number>
, и обрабатывать его как Promise<string>
... который взорвется во время выполнения.
Если бы я пытался быть более простым и более безопасным, я бы ограничил T
до Promise<string>
, Observable<string>
или ReadableStream<string>
и забыл бы отображение, но потребовал, чтобы переданный диспетчер принял string
и вернуть T
:
class Action<T extends Promise<string> | Observable<string> | ReadableStream<string>> {
dispatcher: (x: string) => T;
constructor(dist: (x: string) => T) {
this.dispatcher = dist
}
f = (s: string) => this.dispatcher('hello' + s);
}
// note that x is type string in this:
const promiseDispatcher = (x: string) => Promise.resolve(x)
const a = new Action(promiseDispatcher);
a.f("random string").then((s: string) => {
// ...
})
Хорошо, надеюсь, что один или несколько из них помогут вам. Удачи!