javascript - Hvordan håndtere NodeJS-feil i TypeScript?


Jeg prøver å håndtere unntak kastet av NodeJS ved bruk av TypeScript.
For eksempel, hvis jeg får et ECONNREFUSED-unntak, er det av typen SystemError.

Men akkurat nå, fordi err er av typen any, kan jeg bare gjøre dette

redisClient.on('error', (err) => {
  console.log('Error: ', err);
});

Men jeg vil gjerne avgrense feiltypen, lik dette:

redisClient.on('error', (err) => {
  if (err instanceof SystemError) {
    if(err.code === 'ECONNREFUSED ') {
      throw new Error('Connection refused');
    }
  }
  
  throw err;
});

Men dessverre er SystemError ikke eksportert i Node.
Jeg fant faktisk en diskusjon på GitHub fra for 4 år siden om NodeJS.ErrnoException, men det ser ut til at den ble fjernet.
Er det for øyeblikket mulig i NodeJS?

Svar #1

Merk: Dette diskuteres også på GitHub-issue nodejs/node#46869 — Export SystemError

Jeg trengte å skille systemfeil i typesystemet i noen tidligere prosjekter og fant ingen pakker som gjorde det på en tilfredsstillende måte, så jeg skrev en enkel, utvidbar modul som jeg har brukt siden da — den definerer noen grensesnitt basert på informasjon i Node.js-dokumentasjonen (lenker til kilden inkludert) og type guards som bruker disse grensesnittene:

system_error.ts:

import { getSystemErrorMap } from "node:util";
import { isNativeError } from "node:util/types";

/** https://nodejs.org/docs/latest-v20.x/api/errors.html#class-systemerror */
export interface BaseSystemError<Code extends string = string> extends Error {
  /** The string error code */
  code: Code;

  /** The system-provided error number */
  errno: number;

  /** A system-provided human-readable description of the error */
  message: string;

  /** The name of the system call that triggered the error */
  syscall: string;
}

/** https://nodejs.org/docs/latest-v20.x/api/errors.html#class-systemerror */
export interface SystemError<Code extends string = string>
  extends BaseSystemError<Code> {
  /** If present, the address to which a network connection failed */
  address?: string;

  /** If present, the file path destination when reporting a file system error */
  dest?: string;

  /** If present, extra details about the error condition */
  info?: Record<string, unknown>;

  /** If present, the file path when reporting a file system error */
  path?: string;

  /** If present, the network connection port that is not available */
  port?: number;
}

// Memo (lazily initialized):
// https://nodejs.org/docs/latest-v20.x/api/util.html#utilgetsystemerrormap
let systemErrorMap: ReturnType<typeof getSystemErrorMap> | undefined;

type JsType =
  | "bigint"
  | "boolean"
  | "function"
  | "number"
  | "object"
  | "string"
  | "symbol"
  | "undefined";

export function isSystemError(value: unknown): value is SystemError {
  // https://nodejs.org/docs/latest-v20.x/api/util.html#utiltypesisnativeerrorvalue
  if (!isNativeError(value)) return false;

  for (const [key, jsType] of [
    ["code", "string"],
    ["errno", "number"],
    ["syscall", "string"],
  ] satisfies [keyof SystemError, JsType][]) {
    if (typeof (value as SystemError)[key] !== jsType) return false;
  }

  systemErrorMap ??= getSystemErrorMap();
  return systemErrorMap.has((value as SystemError).errno);
}

export function isSystemErrorWithCode<T extends string>(
  value: unknown,
  code: T,
): value is SystemError<T> {
  return isSystemError(value) && value.code === code;
}

// You can also define type guards for any specific system errors that you want to discriminate…
// https://nodejs.org/docs/latest-v20.x/api/errors.html#common-system-errors

export function isConnectionRefusedError(
  value: unknown,
): value is SystemError<"ECONNREFUSED"> {
  return isSystemErrorWithCode(value, "ECONNREFUSED");
}

export function isNotFoundError(
  value: unknown,
): value is SystemError<"ENOENT"> {
  return isSystemErrorWithCode(value, "ENOENT");
}

// …etc.

Du kan bruke det i koden din slik:

import { isConnectionRefusedError } from "./system_error"

function example() {
  try {
    // statements…
  } catch (cause) {
    if (isConnectionRefusedError(cause)) {
      cause
      //^? SystemError<"ECONNREFUSED">
      cause.code
      //    ^? BaseSystemError<"ECONNREFUSED">.code: "ECONNREFUSED"
    }
  }
}

// or the Redis example in your question:
redisClient.on("error", (err) => {
  if (isConnectionRefusedError(err)) {
    // It's a "connection refused" error; do something…
  } else {
    // do something different…
  }
});

Kode i TypeScript-lekeplassen

Svar #2

Vennligst prøv dette for å håndtere feil i Typescript:

interface NodeSystemError extends Error {
  code?: string;
  errno?: number;
  syscall?: string;
  path?: string;
  address?: string;
  port?: number;
}

redisClient.on('error', (err) => {
  const error = err as NodeSystemError;

  if (error.code === 'ECONNREFUSED') {
    console.error('Connection refused:', error);
    throw new Error('Redis connection refused');
  } else if (error.code === 'EHOSTUNREACH') {
    console.error('Host unreachable:', error);
    throw new Error('Redis host unreachable');
  } else {
    console.error('Unhandled error:', error);
    throw error;
  }
});

vi kan også bruke instanceof

redisClient.on('error', (err) => {
  if (err instanceof Error && (err as any).code === 'ECONNREFUSED') {
    console.error('Connection refused:', err);
    throw new Error('Redis connection refused');
  } else if (err instanceof Error && (err as any).code === 'EHOSTUNREACH') {
    console.error('Host unreachable:', err);
    throw new Error('Redis host unreachable');
  } else {
    console.error('Unhandled error:', err);
    throw err;
  }
});

Relaterte artikler