将未知类型缩小为泛型类型参数

Narrow unknown type to generic type parameter

我有一个调用 Web 套接字的函数,该套接字 returns 序列化结果,然后反序列化。反序列化的结果类型(使用 msgpack)是 unknown.

因此我想为调用函数提供一个泛型类型参数,以便将结果的类型传递给调用函数。

我的尝试是这样的:

function call<T>(response: unknown): T {
  return response; // Type 'unknown' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'unknown'. (2322)
}

TS Playground

我可以限制通用类型,但这是显示我的问题的最简单的再现。

有没有一种方法可以使用泛型类型参数来实现这一点,或者有没有更好的方法来提供传递预期类型的​​可能性?

正如其中一条评论所指出的,使您的示例准确地使用该签名的唯一方法是使用断言。所以,只需将 return 更改为 return response as T

但我认为有更好的方法来处理这种情况,即找出反序列化响应的 return 类型。

我从来都不喜欢这样的解决方案,即使用类型参数来告诉函数 return 什么类型,否则该类型将是 unknown,因为它来自某些外部来源,例如,一个API打电话。您基本上是在告诉编译器我 知道 我一定会取回这种类型。我想在某些情况下这可能是真的,但在 API 通话中,我认为更可能的情况是你知道你 可能 会得到什么,但你不是 100% 确定。毕竟,API 可能会发生变化,服务器可能会出现错误,等等。

我认为更好的方法是使用 user defined type guard 来实际检查您认为的类型。这为您提供了正确的类型和真正的类型安全。

最简单的方法是将 return 类型保留为 unknown,然后将其传递给函数外部的类型保护。因此,例如,假设您期望 return 类型的 Foo:

function isFoo(response: unknown): response is Foo {
  // code that checks the form of the response and
  // returns true if valid
}

function call(response: unknown):unknown {
  return response; 
}

const myResponse
const myReturnedResponse = call(myResponse)
if(isFoo(myReturnedResponse)) {
  //normal execution
} else {
  // error handling
}

但你可以做得更好。您可以保留 call 函数,但不是通过类型参数提供有关预期类型的​​信息,而是将类型保护本身作为运行时参数传递。这将使您保留上面的基本签名,该签名以未知参数和 return 类型的响应开头,但现在您拥有真正的类型安全。

所以--

function call<T>(response:unknown, isCorrectType: (x:unknown)=>x is T):T {
  if(isCorrectType(response)) {
     return response
  } else {
     throw("Invalid response")
  }
}

注意这里还有一个泛型类型参数,但你不需要显式地把它放进去(这实际上只是一个断言),它是从类型保护的 return 类型推断出来的你通过了。

Here's a working example in a playground.