this篇文章的启发,我现在使用了一个返回错误而不是抛出错误的模式(就像在Golang或FP-TS中一样).我通过更多的输入扩展了文章中的代码.这样,就可以在编译时知道函数返回哪种类型的错误.

const ERR = Symbol('ERR');
type Err<ErrType extends string> = {
  [ERR]: true;
  message: string;
  type: ErrType;
};

function isErr<ErrType extends string, Ok>(
  x: Ok | Err<ErrType>,
): x is Err<ErrType> {
  return typeof x === 'object' && x != null && ERR in x;
}

function Err<ErrType extends string>(
  message: string,
  type: ErrType,
): Err<ErrType> {
  return { [ERR]: true, message: message, type: type };
}

您可以使用如下模式(link to playgound):

function errIfFalse(input: boolean) {
    if (input) {
        return Err("input is false", "falseInput")
    }
    return "sucess"
}

function doSomething() {
    const a = errIfFalse(true)
    if (isErr(a)) {
        console.log("error: " + a.message)
        return a.type
    } 
    console.log(a)
    return "no error"
}

const a = doSomething()

到目前为止,函数doSomething()的返回类型被正确地推断为字符串文字"FalseInput"和"No Error"之一.但我对该模式有一个问题:当重构代码库时,以前返回错误的函数不再返回错误.包装函数可能会错误地推断其返回类型.下面的示例中就是这种情况.这可能会在代码库的其他部分造成令人困惑的错误.

function doSomething2() {
    const a = true
    if (isErr(a)) { // should throw compile time error because a is already narrowed down 
        // this code is unreachable but the compiler does not know that
        console.log("error: " + a.message)
        return a.type
    } 
    console.log(a)
    return "no error"
}

我想以某种方式修改类型缩小函数isErr(),以便它只接受尚未缩小的输入.我怎样才能做到这一点呢?

推荐答案

接近你想要的东西的最简单的方法是

function isErr<T>(
  x: T,
): x is Extract<T, Err<any>> {
  return typeof x === 'object' && x != null && ERR in x;
}

本质上,我们不是试图要求输入是多个generic类型的union,而是只有一个泛型类型T,然后通过使用the Extract utility type过滤并集来计算T的"Err部分".这会立即为您提供相同的行为,以获得良好的呼叫:

function doSomething() {
  const a = errIfFalse(true)
  if (isErr(a)) {
    a // Error<"falseInput">
    console.log("error: " + a.message)
    return a.type
  }
  a // "sucess" 
  console.log(a)
  return "no error"
}

对于错误调用,编译器最终将输入范围缩小到the impossible never type:

function doSomething2() {
  const a = true
  if (isErr(a)) { // okay, but
    console.log("error: " + a.message) // error
    //              --------> ~~~~~~~
    //  Property 'message' does not exist on type 'never'.
    return a.type // error
    //   --> ~~~~
    // Property 'type' does not exist on type 'never'.
  }
  console.log(a)
  return "no error"
}

这不是您所要求的,但很明显出了问题.


如果你真的需要禁止通话,除非输入的可能是Err,那么你可以使用generic constraint.理想情况下,您可以将其设置为lower-bound constraint,例如Err<any> extends TT super Err<any>,但TypeScrip仅直接支持upper-bound constraints.在microsoft/TypeScript#14520版本中,这是一个长期悬而未决的问题,但在实现之前,我们需要解决它.您通常可以通过编写T extends (U extends T ? unknown : U)来模拟约束T super U,而the conditional type U extends T ? unknown : U只有在U extends T不为真的情况下才会约束T.所以这看起来像是

function isErr<T extends (Err<any> extends T ? unknown : Err<any>)>(
  x: T,
): x is Extract<T, Err<any>> {
  return typeof x === 'object' && x != null && ERR in x;
}

再说一次,好的预测不会受到影响.但现在,糟糕的预测是这样的:

function doSomething2() {
  const a = true
  if (isErr(a)) { // error
    //  --> ~
    // Argument of type 'boolean' is not assignable to parameter of type 'Err<any>'.
    console.log("error: " + a.message)
    return a.type
  }
  console.log(a)
  return "no error"
}

这会在您想要的位置显示一个错误.


这两种表单都不会以您想要的方式执行可达性分析,但这主要是打字脚本的限制,或者是缺少的功能,在microsoft/TypeScript#12825中有记录.像这样将值缩小到never已经太晚了,无法通过可达性分析注意到.希望这个限制不是什么大问题,因为无论哪种方式,您都会收到do个错误.

Playground link to code

Node.js相关问答推荐

无法从ejs Web应用程序中的正文中提取数据

为什么在导出的函数中调用node-sqlite3中的数据库方法时不起作用?

JsonwebToken过期后如何注销和清除cookie?

如何在Reaction应用程序中查看存储的斑点图像?

更新 mongodb 中的交易值

Amplify 部署的应用程序出现TypeError: handler is not a function错误,但它在本地运行

$not 的聚合版本是什么?

在新创建的 Angular 工作区上运行 ng lint 时出错

在数组的另一个对象中获取数组的mongoose 对象

NestJS 共享模块的问题

Azure Function 在 2022 年 4 月 26 日禁用将用户字段设置为 HTTP 请求对象

如何在 TypeScript 中输出 Hackerrank 二叉树问题?

Firestore promise 在退货前等待产品详细信息

node_modules 中 .bin 文件夹的用途是什么?

Chrome 浏览器未将 if-modified-since 标头发送到服务器

具有多个条件的mongoose 查找

PhoneGap/Cordova Android 开发

路由后的 Node Express 4 中间件

从 JavaScript 文件或 REPL 中 require()'ing CoffeeScript 文件

在 Node.js 中获取终端的宽度