Как сделать тип возвращаемого значения функции не примитивным или Promise не примитивным? - PullRequest
0 голосов
/ 06 декабря 2018

Я пытаюсь создать типизированную функцию, которой разрешено возвращать только не примитивные результаты.Синхронизация или асинхронность.Однако, похоже, что ограничение Promise<object> не реализовано в коде, так как само Promise уже не является примитивным.

function nonPrimitiveResult(): object | Promise<object> {
    return {};                  // OK
    return 1;                   // Error
    return undefined;           // Error
    return Promise.resolve({}); // OK
    return Promise.resolve(1);  // OK, why?
    return Promise.resolve();   // OK, why?
}

Ответы [ 3 ]

0 голосов
/ 06 декабря 2018

Вы хотели бы сказать "Либо object, который совсем не Promise, либо Promise<object>".К сожалению, концепцию "object, которая не является Promise" проще всего выразить как тип вычитания , который в настоящее время не поддерживается в TypeScript.

Один из способов продолжить вместо этого- описать тип объекта, который определенно не является Promise и включает большинство не-примитивов, которые вы, возможно, захотите разрешить, без разрешения всех из них.Например, было бы разумно ожидать, что если возвращаемое значение nonPrimitiveResult() содержит свойство с именем then, то результатом будет Promise.Или, что то же самое, если результат не a Promise, тогда не содержит свойство с именем then.Если это разумный компромисс, вы можете изменить object | Promise<object> на {then?: never} | Promise<object>.Тип {then?: never} означает «это объект без определенного свойства then».

В этом случае ваша подпись становится:

function nonPrimitiveResult(): { then?: never } | Promise<object> {
  return {};                  // OK
  return 1;                   // Error
  return undefined;           // Error
  return Promise.resolve({}); // OK
  return Promise.resolve(1);  // Error
  return Promise.resolve();   // Error      
}

, и все ваши дела ведут себя так, как вы хотели бы.Это обходной путь, конечно.Если утверждение «результат со свойством then должен быть Promise» не применяется на практике, то вы в конечном итоге запретите действительные объекты:

function nonPrimitiveResult(): { then?: never } | Promise<object> {
  return {then: "theDinosaursCame"};  // Error, uh oh
}

Так что это не идеальное решение.Вы можете быть более умным и начать описывать объединение типов, которое более точно представляет «объект, который не является Promise», например:

type NotAFunction = string | number | boolean | null | undefined | { call?: NotAFunction };
declare function nonPrimitiveResult(): { then?: NotAFunction } | Promise<object>;

, но которое становится все более и более сложным с меньшими затратами.и меньше предельной выгоды.Поэтому я, вероятно, просто придерживаюсь {then?: never}, если не столкнусь с вариантом использования, который сделает его неработоспособным.

В любом случае, надеюсь, это поможет.Удачи!

0 голосов
/ 11 декабря 2018

Как вы знаете, не примитивы в JS (и, следовательно, TypeScript) включают objectarray, который все еще остается за кадром).

Таким образом, самый буквальный способ ответить на вашвопрос:

async function nonPrimitiveResult(): Promise<object> {
   // ... implementation
}

В качестве объяснения этого ответа давайте углубимся в то, почему вы получили странные результаты в вашей IDE.

Объясните, почему вы видели то, что видели

Причина, по которой вы получаете странный ложноположительный результат, заключается в том, что вы не помечаете функцию как async.Если бы вы сделали это, все стало бы намного яснее.

async function nonPrimitiveResult(): Promise<object> {
    return Promise.resolve(1); // Type 'number' is not assignable to type 'object'.
}

Больше контекста:

1) Почему вы всегда должны отмечать каждую функцию, которая возвращаетОбещание async

  • В вашем исходном примере переданный (то есть результирующий код javascript) не был бы помещен в генератор.Это означает, что nonPrimitiveResult возвратил бы объект Promise, который на самом деле является объектом - это просто объект, на котором есть функция then.Поэтому я бы порекомендовал использовать tslint, чтобы заставить вас всегда ожидать функцию, которая возвращает обещание.В частности, вам нужно правило promise-function-async: https://palantir.github.io/tslint/rules/promise-function-async/

2) При возврате объекта или обещания

  • После того, как вы пометите функцию как async вы обнаружите, что больше не можете представлять тип ответа как object | Promise<object>, потому что получите эту ошибку:
    • The return type of an async function or method must be the global Promise<T> type.
  • Почему? Поскольку результатом любой асинхронной функции является Обещание какого-то результата.
0 голосов
/ 06 декабря 2018

Вероятно, это далеко не идеальное решение, но вы можете смоделировать тип поведения, который вам нужен, с помощью условного типа.Порядок условного типа здесь важен, потому что мы отбрасываем Promise<Primitive> перед проверкой на другие типы обещаний.

Вы можете произвольно сделать этот пример более строгим, добавив больше ветвей к условному типу, чтобы покрытьсценарии, которые вам не нужны.

К сожалению, чтобы это работало, оператору return необходимо выполнить приведение к универсальному типу:

type Primitive = number | string | boolean;

type X<T> = T extends Primitive ?
  never
  : T extends Promise<Primitive> ?
  never
  : T extends Promise<any> ?
  Promise<T>
  : T;

const nonPrimitiveResult = <T>(): X<T> => {
  return 1 as X<T>; // error
  return true as X<T>; // error
  return "hello world" as X<T>; // error

  return Promise.resolve(1) as X<T>; // error
  return Promise.resolve(true) as X<T>; // error
  return Promise.resolve("hello world") as X<T>; // error

  return Promise.resolve() as X<T>; // error

  return {} as X<T>; // ok
  return Promise.resolve({}) as X<T>; // ok
}
...