在 Typescript 中,如何区分 Node 和普通 Javascript 错误类型?

Tom*_*Tom 12 javascript node.js typescript

我有以下功能:

/**
 * 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
}
Run Code Online (Sandbox Code Playgroud)

打字稿在这里抱怨:

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

这是因为 JavascriptError类只有属性message 和 name

然而,Node 的Error类确实有一个code 属性

Typescript 在一个特殊的接口中定义了它ErrnoException(请参阅此处的源代码)。我已经添加@types/node到我的 package.json 中,但这并没有让 Typescript 意识到这Error是接口的一部分ErrnoException

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

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

{
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "lib": ["es2017"]
    ...
  }
}
Run Code Online (Sandbox Code Playgroud)

Gur*_*ofu 8

类型安全的 TypeScript 解决方案

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

interface ErrnoException extends Error {
   errno?: number | undefined;
   code?: string | undefined;
   path?: string | undefined;
   syscall?: string | undefined;
}
Run Code Online (Sandbox Code Playgroud)

下面的类型防护完全尊重这种辩护。我的 TypeScript 和 ESLint 设置非常严格,因此您很可能不需要禁用 ESLint/TSLint 的注释(如果您仍然使用这个已弃用的注释)。

interface ErrnoException extends Error {
   errno?: number | undefined;
   code?: string | undefined;
   path?: string | undefined;
   syscall?: string | undefined;
}
Run Code Online (Sandbox Code Playgroud)

在哪里

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");
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以检查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();
}
Run Code Online (Sandbox Code Playgroud)

我在我的库@yamato-daiwa/es-extensions-nodejs中添加了isErrnoException类型保护,但因为我知道第三方解决方案的推广可能很烦人,所以我已经发布了上面使用示例的完整实现 =)


Tom*_*Tom 5

我最终使用了@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
Run Code Online (Sandbox Code Playgroud)

但我惊讶地发现这是必要的。它还要求您禁用 tslint 的unsafe-any 规则(如果您正在使用该规则)。


Hug*_*ira 1

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

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

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