У меня есть функция, которая может возвращать синхронный или асинхронный результат.
type HookHandler<T> = (context: MyClass<T>) => boolean | Promise<boolean>;
и класс, который принимает список этих функций
class MyClass<T> {
constructor(private handlers: Array<HookHandler<T>>) {
}
public invokeHandlers() : boolean | Promise<boolean> {
// invoke each handler and return:
// - Promise<boolean> if exist a handler that return a Promise<T>
// - boolean if all handlers are synchronous
}
}
Мне было интересно, есть ли шанс заставить машинописный текст вывести тип возвращаемого значения invokeHandlers() на основе заданных обработчиков. Учтите, что все обработчики объявлены во время разработки:
const myClassSync = new MyClass<MyType>([
(ctx) => true,
(ctx) => false
]);
const myClassAsync = new MyClass<MyType>([
async (ctx) => Promise.resolve(true),
async (ctx) => Promise.reject()
]);
const myClassMix = new MyClass<MyType>([
async (ctx) => Promise.resolve(true),
(ctx) => true
]);
Могу ли я сделать возвращаемый тип invokeHandlers() зависимым от типов текущих заданных обработчиков без явного приведения? Так например
// all handlers are sync, infer boolean
const allHandlersAreOk: boolean = myClassSync.invokeHandlers()
// all handlers are async, infer Promise<boolean>
const allAsyncHandlersAreOk: Promise<boolean> = await myClassAsync.invokeHandlers()
// at least one handler is async, infer Promise<boolean>
const allMixedHandlersAreOk: Promise<boolean> = await myClassMix.invokeHandlers()
Очевидно, я могу вернуть простой Promise<boolean>, но я бы потерял возможность вызывать invokeHandlers() в синхронном контексте, а этого хотелось бы избежать.
Любые предложения или другой выбор дизайна для решения проблемы? Спасибо!
Общий тип скопирован из реального кода, это просто упрощение.






То, что некоторые из них могут возвращать обещания, является фактом. Это самый известный TypeScript может.
Являются ли они возвращаемыми промисами или нет, можно определить только во время выполнения.
Итак, ответ — нет, TypeScript не может вывести что-то, что можно вывести только во время выполнения.
Имеет смысл. Может быть для вас решением разделить invokeHandlers() на две функции, одна из которых возвращает boolean, а другая возвращает Promise<boolean>?
Да, но тогда тот, который может возвращать только boolean, должен быть набран таким образом, чтобы его параметры никогда не были промисами, что вынуждает вас (как программиста) использовать правильный с самого начала. И это в основном устранило бы преимущество возможности смешивать асинхронные и синхронизирующие обработчики. ИЛИ вы можете сделать проверку во время выполнения, а не во время компиляции, но тогда вы потеряете преимущества TypeScript.
Да, в этом была суть. В настоящее время я игнорирую асинхронные обработчики, если вызывающая сторона использует синхронизированную версию invokeHandlers(), но я искал более чистое решение.
вы можете использовать перегрузки, если у вас есть способ различать ваши обработчики или каким-то образом идентифицировать их во время выполнения.
function handler(x: number): string;
function handler(y: string): number;
function handler(arg) {
if (typeof arg === 'number') {
return `${arg}`
} else {
return parseInt(arg);
}
}
const inferred = handler(1); // <-- typescript correctly infers string
const alsoInferred = handler('1'); // <-- typescript correctly infers number
Итак, если бы вы могли написать что-то вроде:
function handler(context: AsyncHandler): Promise<boolean>;
function handler(context: MixedHandlers): Promise<boolean>;
function handler(context: SyncHandlers): boolean:
function handler(context){
// your implementation, maybe instanceof if each type has a class representation
}
TypeScript может правильно определить возвращаемый тип. Я не уверен, возможно ли это на основе вашей структуры кода, но я решил поделиться. Подробнее здесь, особенно раздел о перегрузках
Спасибо, я попробую
Вот как я бы подошел к этому:
Придумайте отдельные типы для каждого возможного обработчика хука:
type SyncHookHandler = (context: MyClass<any>) => boolean;
type AsyncHookHandler = (context: MyClass<any>) => Promise<boolean>;
type HookHandler = AsyncHookHandler | SyncHookHandler;
А затем сделайте MyClass зависимым от типа HHHookHandler, который вы используете. Тип возвращаемого значения invokeHandlers может быть условный тип, который оценивается как boolean, если HH равно SyncHookHandler, и Promise<boolean>, если HH равно AsyncHookHandler или AsyncHookHandler | SyncHookHandler:
class MyClass<HH extends HookHandler> {
constructor(private handlers: Array<HH>) { }
public invokeHandlers(): Promise<boolean> extends ReturnType<HH> ?
Promise<boolean> : boolean;
public invokeHandlers(): boolean | Promise<boolean> {
const rets = this.handlers.map(h => h(this));
const firstPromise = rets.find(r => typeof r !== 'boolean');
if (firstPromise) {
return firstPromise; // ? what do you want to return here
}
// must be all booleans
const allBooleanRets = rets as boolean[];
return allBooleanRets.every(b => b); // ? what do you want to return here
}
}
Я только что сделал глупую реализацию внутри invokeHandlers(), чтобы дать представление о том, что вы будете там делать. Теперь вы можете видеть, что ваш код ведет себя так, как ожидалось.
const myClassSync = new MyClass([
(ctx) => true,
(ctx) => false
]);
// all handlers are sync, infer boolean
const allHandlersAreOk: boolean = myClassSync.invokeHandlers()
const myClassAsync = new MyClass([
async (ctx) => Promise.resolve(true),
async (ctx) => Promise.reject()
]);
// all handlers are async, infer Promise<boolean>
// note you do not "await" it, since you want a Promise
const allAsyncHandlersAreOk: Promise<boolean> = myClassAsync.invokeHandlers()
const myClassMix = new MyClass([
async (ctx) => Promise.resolve(true),
(ctx) => true
]);
// at least one handler is async, infer Promise<boolean>
// note you do not "await" it, since you want a Promise
const allMixedHandlersAreOk: Promise<boolean> = myClassMix.invokeHandlers()
Это работает для вас?
Обратите внимание, что поскольку код примера не имел структурной зависимости от универсального параметра T, у меня удалил это. Если вам это нужно, вы можете добавить его обратно в соответствующие места, но я предполагаю, что вопрос больше касается обнаружения синхронизации, если вы можете, а не какого-то общего типа.
Хорошо, надеюсь, это поможет; удачи!
Возможно, у меня есть решение для вас, но ваш код не имеет структурной зависимости от параметра
T, поэтому я планирую его исключить.