Условные типы Typescript, выведенные функцией высокого порядка - PullRequest
0 голосов
/ 28 февраля 2019

У меня есть функция, которая может возвращать результат синхронизации или асинхронности

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() в синхронных контекстах, и он хочет этого избежать.

Какие-либо предложения или другой дизайн, чтобы столкнуться с проблемой?Спасибо!

Ответы [ 3 ]

0 голосов
/ 28 февраля 2019

вы можете использовать перегрузки, если у вас есть способ различать ваши обработчики или идентифицировать их каким-либо образом во время выполнения

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 может правильно определить тип возвращаемого значения.Я не уверен, возможно ли это на основе вашей структуры кода, но я думал, что поделюсь.Подробнее здесь , в частности, раздел о перегрузках

0 голосов
/ 28 февраля 2019

Вот как я подхожу к этому:

Придумайте отдельные типы для каждого возможного обработчика крюка:

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 удалил его .Если вам это нужно, вы можете добавить его обратно в соответствующие места, но я предполагаю, что вопрос больше в том, чтобы обнаружить-синхронизировать, если вы можете, а не о каком-то универсальном типе.

Хорошо, надеюсь, это поможет;удачи!

0 голосов
/ 28 февраля 2019

То, что некоторые из них могут вернуть обещания, является фактом.Это большинство TypeScript может знать.

Если они или не все возвращаемые обещания могут быть определены только во время выполнения.

Таким образом, ответ - нет, TypeScript может 't выводит что-то, что может быть заразно только во время выполнения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...