【问题标题】:In Typescript how do you make a distinction between Node and vanilla Javascript Error types?在 Typescript 中,如何区分 Node 和 vanilla Javascript 错误类型?
【发布时间】:2019-01-02 13:05:04
【问题描述】:

我有以下功能:

/**
 * Retrieves a component template from filesystem
 */
const getComponentTemplate = async (
  p: string
): Promise<string> => {
  let template: string
  try {
    template = await fs.readFile(p, {
      encoding: 'utf8'
    })
  } catch (e) {
    if (e instanceof Error && e.code === 'ENOENT') {
      throw new Error(`template for element type ${elementType} not found`)
    }
    throw e
  }

  return template
}

Typescript 在这里抱怨:

[ts] Property 'code' does not exist on type 'Error'

这是因为 Javascript Error 类只有属性 message and name

但是,Node 的 Error 类确实有一个 code property

Typescript 在一个特殊的接口ErrnoException 中定义了这个(参见源代码here)。我已将@types/node 添加到我的package.json 中,但这并没有让Typescript 意识到这个ErrorErrnoException 接口的一部分。

不能在 catch 子句中声明类型注释。那么,如何让 Typescript 编译器能够解决这是一个节点错误呢?

仅供参考,这是我tsconfig.json 的一部分:

{
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "lib": ["es2017"]
    ...
  }
}

【问题讨论】:

  • e instanceof Error 您正在测试它是否是Error 而不是ErrnoException。我不确定告诉它正确类型的最佳方式,但“快速和肮脏”的方式是e instanceof Error &amp;&amp; (e as ErrnoException).code === 'ENOENT'
  • 您可能可以按照以下方式制作类型保护:function isError(error: any): error is ErrnoException { return error instanceof Error; } 尽管如果这样的东西不存在,我会感到惊讶。你会这将是一个常见的要求!
  • 我意识到我可以使用as,但这似乎有点脏。 Typescript 不应该知道我在 Node 中运行吗?
  • if (e instanceof ErrnoException) 不能满足您的需求吗?
  • @arvymetal 否,因为该类实际上是 Error(Node 提供的那个)。 ErrnoException 只是一个接口,不是一个类。因此 e 不是它的一个实例。请注意,Node 的 Error 实现了 ErrnoException,但由于某种原因 Typescript 没有实现这一点。

标签: javascript node.js typescript


【解决方案1】:

如果你想使用 try/catch,那么你会得到一个你不知道类型的对象。

您已经测试了该对象是否为 Error 的代码,如果是,则将其转换为“普通”JS Error 对象。

您可以使用typeguard 告诉类型系统对象实际是什么类型。

类似的东西:

function isError(error: any): error is ErrnoException { return error instanceof Error; }

我查看了fs.readFile,它似乎是使用该函数的一种常见方式,实际上是整个节点 api,是通过向它传递一个回调,该回调在工作完成时被调用,或者有一个错误。

查看type definition 表明传递给回调的错误对象确实是所需的ErrnoException

export function readFile(path: PathLike | number, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void;

因此使用回调将消除对类型保护的需要,并且似乎是接近这一点的节点方式。

This article 显然详细说明了“回调所有事物”方法背后的一些想法。

Node 大量使用回调可以追溯到一种编程风格 比 JavaScript 本身更早。延续传递风格(CPS)是 Node.js 今天如何使用回调的老派名称。在 CPS 中,一个 “延续函数”(读作:“回调”)作为参数传递给 一旦该代码的其余部分已经运行,就会被调用。这允许 不同的功能来异步手动控制来回 跨应用程序。

Node.js 依靠异步代码来保持快速,因此拥有一个 可靠的回调模式至关重要。没有一个,开发人员将 被困在每个和之间保持不同的签名和风格 每个模块。错误优先模式被引入 Node 核心 解决了这个问题,并从此传播到今天 标准。虽然每个用例都有不同的要求和 响应,错误优先模式可以容纳所有这些。

【讨论】:

  • 感谢您的意见! typeguard 似乎解决了这个问题,尽管我必须使用 tslint 忽略注释才能使用 any。那个支持回调的帖子是从 2014 年开始的。从那时起,情况发生了很大变化。人们倾向于避免回调以避免深度嵌套的结构。 Async / await 被大量采用,我不认为它是回到过去的解决方案,因为 Typescript 无法解决 Error 类实际上来自 node 而不是 es6。
  • 我愿意接受您对 Typeguard 的回答,但不接受回调建议。 ;)
【解决方案2】:

我最终使用了@AndyJ 的评论:

/**
 * Retrieves a component template from filesystem
 */
const getComponentTemplate = async (
  p: string
): Promise<string> => {
  let template: string
  try {
    template = await fs.readFile(p, {
      encoding: 'utf8'
    })
  } catch (e) {
    // tslint:disable-next-line:no-unsafe-any
    if (isNodeError(e) && e.code === 'ENOENT') {
      throw new Error(`template for element type ${elementType} not found`)
    }
    throw e
  }

  return template
}

/**
 * @param error the error object.
 * @returns if given error object is a NodeJS error.
 */
const isNodeError = (error: Error): error is NodeJS.ErrnoException =>
  error instanceof Error

但我惊讶地发现这是必要的。如果您正在使用它,它还要求您使用disable tslint's unsafe-any 规则。

【讨论】:

  • 难以置信,这仍然是解决这个问题的最佳方案。
【解决方案3】:

您可以考虑使用方括号读取code 属性,然后检查其值是否等于ENOENT

try {
    ...
} catch (e) {
    const code: string = e['code'];
    if (code === 'ENOENT') {
        ...
    }
    throw e
}

这不是一个完美的解决方案,但考虑到您无法在 catch 子句中声明类型并且 e instanceof ErrnoException 检查无法正常工作(如问题 cmets 中所述),它可能已经足够了。

【讨论】:

  • 谢谢——考虑到错误处理的普遍性,如果这是推荐的方法,我会感到惊讶。
【解决方案4】:

类型安全的 TypeScript 解决方案

这不是通用解决方案,但适用于ErrnoException 情况。 根据"@types/node": "16.11.xx" 定义,ErrnoException 是接口:

interface ErrnoException extends Error {
   errno?: number | undefined;
   code?: string | undefined;
   path?: string | undefined;
   syscall?: string | undefined;
}

Below type guard 完全尊重这一定义。我的 TypeScript 和 ESLint 设置非常严格,因此很有可能您不需要 cmets 禁用 ESLint/TSLint(如果您仍然使用这个被贬低的)。

function isErrnoException(error: unknown): error is ErrnoException {
  return isArbitraryObject(error) &&
    error instanceof Error &&
    (typeof error.errno === "number" || typeof error.errno === "undefined") &&
    (typeof error.code === "string" || typeof error.code === "undefined") &&
    (typeof error.path === "string" || typeof error.path === "undefined") &&
    (typeof error.syscall === "string" || typeof error.syscall === "undefined");
}

在哪里

type ArbitraryObject = { [key: string]: unknown; };

function isArbitraryObject(potentialObject: unknown): potentialObject is ArbitraryObject {
  return typeof potentialObject === "object" && potentialObject !== null;
}

现在我们可以检查code 属性:

import FileSystem from "fs";
import PromisfiedFileSystem from "fs/promises";

// ...

let targetFileStatistics: FileSystem.Stats;

try {

  targetFileStatistics = await PromisfiedFileSystem.stat(validAbsolutePathToPublicFile);

} catch (error: unknown) {

  if (isErrnoException(error) && error.code === "ENOENT") {

     response.
         writeHead(HTTP_StatusCodes.notFound, "File not found.").
         end();

     return;
  }

 
  response.
      writeHead(HTTP_StatusCodes.internalServerError, "Error occurred.").
      end();
}

【讨论】:

    猜你喜欢
    • 2017-10-27
    • 1970-01-01
    • 2021-11-03
    • 1970-01-01
    • 1970-01-01
    • 2018-12-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多