如何使函数返回类型为非原始或Promise非原始?

时间:2018-12-06 13:35:29

标签: typescript generics promise constraints

我正在尝试创建一个仅允许返回非原始结果的类型化函数。同步或异步。但是,似乎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 :(得分:0)

虽然可能不是完美的解决方案,但您可以使用条件类型对行为类型进行建模。条件类型的顺序很重要,因为在检查其他类型的Promise之前,我们将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
}

答案 1 :(得分:0)

您想说“要么根本不是object的{​​{1}},要么是Promise”。不幸的是,“不是Promise<object>的{​​{1}}这个概念最容易表达为subtraction type,而TypeScript目前不支持这种概念。

一种继续进行操作的方法是描述一个绝对不是object的对象类型,并且包括您可能希望允许的 most 个非基元,而不允许所有它们。例如,可以合理地预期,如果Promise的返回值包含一个名为Promise的属性,则结果为nonPrimitiveResult()。或等效地,如果结果为 not then,则 not 包含名为Promise的属性。如果这是一个合理的折衷方案,则可以将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

无论如何,希望能有所帮助。祝你好运!

答案 2 :(得分:0)

您知道,JS(因此还有TypeScript)中的非基元包括object(和array,后者仍然是幕后对象)。

所以回答您问题的最直接的方法是:

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)为什么要始终将每个返回promise的函数标记为异步

  • 在您的原始示例中,已编译的代码(即生成的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.
  • 为什么?因为任何异步函数的结果都是某天结果的承诺。