The Problem with Handling Node.js Errors in TypeScript (and the workaround)
Tags: typescript, javascript, node, webdev
Published: 2020-11-25 11:06:52

# The Setup

So, I was recently using Node's `url` module within TypeScript to be able to do some simple validation of user-provided URLs. [According to the docs](https://nodejs.org/api/url.html#url_new_url_input_base), when an invalid URL is supplied to the `URL` class, it throws a `TypeError`. Great! This is exactly what I wanted.

Next, all I had to do was catch that particular `TypeError` and give a helpful message to the user to let them know their URL was no good. Easy, all I need to do is write a `try-catch` statement and check the [error's `code`](https://nodejs.org/api/errors.html#errors_error_code). Of course, the specific error code to look for is [documented on an entirely different page](https://nodejs.org/api/all.html#errors_err_invalid_url) for some reason. It was actually easier for me to just spin up a terminal and write a gibberish string into a `new URL()` call myself to determine that I was looking for `"ERR_INVALID_URL"`.

# The Problematic Code

```typescript
try {
	const validUrl = new URL(url).href;
} catch (e) {
	if (e instanceof TypeError) {
		if (e.code === "ERR_INVALID_URL") {
			// Property 'code' does not exist on
			// type 'TypeError'. ts(2339)
		}
	}
}
```

Huh? What do you mean? The docs clearly stated that an `Error` in Node should have a `code` property, and [`TypeError` extends `Error`](https://nodejs.org/api/errors.html#errors_class_typeerror)... This didn't make sense. 

I used VS Code's nifty "Go to Definition" feature to find the type definition for `TypeError`, which opened `node_modules\typescript\lib\lib.es5.d.ts`. I then found my way to the definition for the `Error` interface...

```typescript
/* node_modules\typescript\lib\lib.es5.d.ts */
interface Error {
    name: string;
    message: string;
    stack?: string;
}
```

Oh! This was the interface of an [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) you'd find in a browser environment.

But I was working with Node, and I already had the [@types/node package](https://www.npmjs.com/package/@types/node) installed... I had falsely assumed that this would somehow magically tell the TypeScript linter that I was catching a Node `Error`. How was I supposed to get TypeScript to infer that the `TypeError` I was handling most likely extended Node's `Error` class, and had the extra `code` property I was looking for?

# Search Engine Spelunking

After some confused finagling with my `tsconfig.json` and VS Code settings, I quickly gave up and went to Google. I soon after learned two things via random answers on StackOverflow:

1. The type definition for the NodeJS `Error` class is declared in `node_modules\@types\node\globals.d.ts` -- and was accessible as `NodeJS.ErrnoException`. I wasn't sure where this was officially documented, but alright!

	```typescript
	/* node_modules\@types\node\globals.d.ts */
	interface ErrnoException extends Error {
			errno?: number;
			code?: string;
			path?: string;
			syscall?: string;
			stack?: string;
	}
	```

2. It was possible to use [TypeScript's type guards](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards) to create a function that I could use to check the error at runtime, so that I (and TypeScript) could be absolutely sure that this variable was a Node `Error`.

The example function from StackOverflow looked sort of like this:

```typescript
function isError(error: any): error is NodeJS.ErrnoException {
	return error instanceof Error;
}
```

At a glance, this seemed like it would work... The function was running an `instanceof` check and used a "type predicate" (the `error is NodeJS.ErrnoException` part) to help TypeScript to do the type inference I was looking for. I could finally access the `code` property on the error without any dreaded red squiggly lines.

```typescript
if (isError(e) && e instanceof TypeError) {
	if (e.code === "ERR_INVALID_URL") {
		// Hooray?
	}
}
```

But, I wasn't totally satisfied. For one, there was nothing stopping me from passing things that weren't errors to `isError()`. This was easily fixed by changing the the first argument of `isError()` to expect `Error` instead of `any`.

Secondly, it also felt inherently silly to have to run two `instanceof` checks every time I wanted to handle an error. (Truthfully, it's not the worst thing in the world... but I believe that TypeScript should require developers to make as few runtime code changes as possible when transitioning from JavaScript.)

# The Solution

After some experimenting, I managed to come up with the following function, which I tested with a couple of custom error classes to ensure that any additionally defined properties were preserved.

It turned out that the key was to make a [generic function](https://www.typescriptlang.org/docs/handbook/generics.html) which acted as a typeguarded version of `instanceof` for Node.JS error handling, by doing the following things:

1. Accepted two arguments that would be similar to the left-hand and right-hand sides of the `instanceof` operator.

2. Enforced the first argument was of the `Error` class or a subclass.

3. Enforced the second argument was a constructor for an `Error` or a subclass of `Error`.

4. Ran the `instanceof` check.

5. Used a type predicate to intersect the type of the first argument with the instance type of the error constructor in the second argument, as well as `NodeJS.ErrnoException` so that type inference would work as expected when used.

```typescript
/**
 * A typeguarded version of `instanceof Error` for NodeJS.
 * @author Joseph JDBar Barron
 * @link https://dev.to/jdbar
 */
export function instanceOfNodeError<T extends new (...args: any) => Error>(
	value: Error,
	errorType: T
): value is InstanceType<T> & NodeJS.ErrnoException {
	return value instanceof errorType;
}
```

## Examples
### Original Use Case
```typescript
try {
	const validUrl = new URL(url).href;
} catch (e) {
	if (instanceOfNodeError(e, TypeError)) {
		if (e.code === "ERR_INVALID_URL") {
			// Hooray!
		}
	}
}
```

### Usage with Custom Error Classes
```typescript
// Create our custom error classes.
class CoolError extends Error {
	foo: string = "Hello world.";
}

class VeryCoolError extends CoolError {
	bar: string = "Goodbye world.";
}

// Try throwing an error.
try {
	throw new CoolError();
} catch (e) {
	if (instanceOfNodeError(e, CoolError)) {
		// typeof e: CoolError & NodeJS.ErrnoException
		console.log(e.foo);
	} else if (instanceOfNodeError(e, VeryCoolError)) {
		// typeof e: VeryCoolError & NodeJS.ErrnoException
		console.log(e.foo, e.bar);
	} else {
		// typeof e: any
		console.log(e);
	}
}

// Try passing something that's not an error.
const c = NaN;
if (instanceOfNodeError(c, CoolError)) {
	// Argument of type 'number' is not assignable to\
	// parameter of type 'Error'. ts(2345)
	console.log(c.foo);
}

const d = new CoolError();
if (instanceOfNodeError(d, Number)) {
	// Argument of type 'NumberConstructor' is not assignable
	// to parameter of type 'new (...args: any) => Error'.
	console.log(d.foo);
}
```

You might be wondering why in that one `else` clause, the type of `e` was `any`... well, TypeScript can't guarantee the type of `e` is anything in particular, because JavaScript lets you `throw` literally anything. Thanks JavaScript...

# Summary

After utilizing both generics and type guards, I managed to get TypeScript to correctly infer the shape of the errors I was handling in a Node.js environment without performing redundant `instanceof` checks. However, the solution still wasn't perfect, since I probably did sacrifice some amount of compute overhead and space on the call stack to be able to call the `instanceOfNodeError()` function compared to the bog-standard `instanceof` call I would have done in JavaScript.

---

It's possible that in the future, there could be an update to the [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) package that would merge the `NodeJS.ErrnoException` type with the global `Error` type. 

One could argue that since not all errors in Node.js will have the `code` property (or the other properties on the `ErrnoException` type), that it doesn't make sense to do such a reckless merging of types. However, I don't see a lot of harm when all of the properties of `ErrnoException` are marked optional.

Otherwise, they have to be manually added to any modules that might throw an `Error` with the properties of `ErrnoException`, [per the details of this rather old commit responsible for implementing it within the `fs` module.](https://github.com/DefinitelyTyped/DefinitelyTyped/commit/cddd0b7aab18761214d26a0c7012cf45de5285a9) However, this still leaves us with a problem when these `ErrnoException` errors can be thrown by the constructors of classes in Node.js, like the `URL` class does.

For that, the only alternative fix I could think of would be for TypeScript to add some sort of `throws` syntax for function/constructor signatures -- which there seems to be [an open issue for from 2016 in the microsoft/TypeScript GitHub repo.](https://github.com/microsoft/TypeScript/issues/13219)