TL/DR: An extendable error class that actually works with TypeScript and ES6 support compatible with all environments, even very old browsers.
This package provides an extendable error class, ExtendableError
for
JavaScript / TypeScript. There exist a number of similar packages on NPM, but
they all have shortcomings, like not supporting TypeScript, being awkward to use
with TypeScript 2, not being compatible with old browsers, not printing the
stack trace when used with console.log
or similar functions or not having all
features I wanted. So I decided to write my own.
Obviously, it's not all 100% written from scratch, but rather I collected the good parts from various existing open source error packages and some StackOverflow answers, fixed various errors, wrote some tests and put it together in one package.
The purpose of extendable errors classes is to be able to throw error objects
that are subclasses of the built-in JavaScript Error
class, which has a number
of gotchas, and to then filter them with the instanceof
operator in the
catch
clause of a try
/catch
block as well as potentially adding additional
variables to the error object.
This ExtendableError
class will:
- subclass the built-in
Error
class. Subclasses created fromExtendableError
will subclassError
,ExtendableError
and any other classes in the inheritance chain. - have a name attribute equal to the class name
- have a string representation with
toString()
that includes the name and message properties of the error object. This is also fixed for all versions of IE, where the error object usually does not print error objects like this. - include all non-standard properties that default
Error
objects provide in different browsers - provide a stack trace, if default
Error
objects have thestack
property or theError.captureStackTrace
exists (on V8, so in Chrome and node.js). Additionally, on V8, the stack trace will not include the constructor functions of the error subclasses. - have a stack trace with the actual error name instead of
Error
- display the stack trace (or the toString() representation, if the stack trace
does not exist), including the error name at the beginning, when printed with
console.log(e)
(except for Chrome, where this does not work, even though thestack
property includes the actual name. I don't think it is possible to fix this, but if anyone knows a way, let me know!)
It is compatible with node.js, provides an old-style CommonJS module and a new-style ES6 module as well as a TypeScript definition file. It is extensively tested and works in node.js and all browsers I have tested (including IE6 and various old browsers as well as mobile browsers).
It's also really small, with less than 200 lines of code, and it has no production dependencies.
You can install the ts-error package from NPM with the command:
# If you use yarn
yarn add ts-error
# If you use NPM
npm install ts-error
Simply import the package and optionally subclass ExtendableError
and create a
new error object. The message
property that should be passed to the object
will is optional and will default to an empty string. If undefined is passed,
this is also turned into an empty string.
For compatiblity, the package requires various methods, that are not defined in old browsers. The CommonJs version (only!) includes polyfills for these functions without polluting the global namespace, if the required functions are not defined. If you want to use your own polyfills, load them before loading this package.
In TypeScript:
import { ExtendableError } from "ts-error";
class CustomError extends ExtendableError {}
try {
throw new CustomError("Optional Error message");
} catch (e) {
if (e instanceof CustomError) {
// ...
} else {
// ...
}
}
In ES6 / esnext:
import { ExtendableError } from "ts-error";
class CustomError extends ExtendableError {}
try {
throw new CustomError("Optional Error message");
} catch (e) {
if (e instanceof CustomError) {
// ...
} else {
// ...
}
}
In ES5:
var ExtendableError = require("ts-error").ExtendableError;
// This is taken from TypeScript compiler output, because it works quite reliably.
// There are various other methods though, so use whatever you like, if you have to use ES5.
var __extends =
(this && this.__extends) ||
(function() {
var extendStatics =
Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array &&
function(d, b) {
d.__proto__ = b;
}) ||
function(d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
};
return function(d, b) {
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype =
b === null
? Object.create(b)
: ((__.prototype = b.prototype), new __());
};
})();
var CustomError = /** @class */ (function(_super) {
__extends(CustomError, _super);
function CustomError() {
return (_super !== null && _super.apply(this, arguments)) || this;
}
return CustomError;
})(ExtendableError);
try {
throw new CustomError("Optional Error message");
} catch (e) {
if (e instanceof CustomError) {
// ...
} else {
// ...
}
}
Please note that error names will not be displayed correctly if the error
class definitions get uglified, because the display of error messages relies on
Function.prototype.name
to infer the correct error message. You can
configure any uglifier to ignore certain function or class names, either via a
source code annotation or using a regex configuration parameter to decide which
properties to mangle.
The module is extensively tested in JavaScript, ES6+ and TypeScript and works in node.js as well as all browsers I have tested: All versions of Chrome 15+, Firefox 3+, Safari 4+, Edge 14+, IE6+, Opera 10.6+, Yandex 14.12, various iOS browsers down to the iPhone 3GS running iOS3 and the Android browser down to Android 4. Note that I haven't test all versions of these browsers (except for IE and Edge due to their notorious buggyness), but rather the oldest few and more recent ones, because I would assume if they work in either of these, they will work in all versions.
If you encounter any issues, please file an issue and I will investigate and fix it.
You can run all tests together with npm run test
, which will build all browser
tests and then execute the node and browser tests sequentially. All tests are
written in TypeScript and compiled to various targets to ensure compatibility.
All build configuration is in tests/build
.
If you choose to build and run tests manually / individually, you first need to
run npm run pretest:create-lib-symlinks
, which creates symlinks of the
TypeScript definition file in the lib
directory.
The node.js tests use mocha
and chai
together with ts-node
. The test
source code is in tests/node
. For node.js testing, the following commands
exist:
npm run test:node:cjs
: Test the CommonJs modulelib/cjs.js
with the compile target ES3.npm run test:node:es
: Test the ES6+ modulelib/es.js
with the compile targetesnext
.npm run test:node
: Run the CJS followed by the ES6+ tests.
Browser testing is a bit more complex. To ensure that the package is compatible
even with the oldest browsers, I had to create a few helper functions to emulate
the required functionality of mocha
and chai
. Old browsers like IE do not
have a console and various quirks, so it is necessary to execute the tests after
the DOM has loaded and write the results to the HTML body.
You can build all browser tests with npm run build:test
as well as in watch
mode with npm run build:test:watch
. The test source code is in
tests/browser/src
. This will run webpack to compile and bundle the test files
for various targets and copy some HTML files. The output will be in
tests/browser/dist
, the bundled JS in tests/browser/dist/js
. You can then
start the test with npm run test:browser
. This will start lite-server
, serve
the compiled files and should open a browser window automatically.
In the browser, you will see a navigation for testing of the scripts compiled to targets ES3, ES5, ES6 and esnext. For each of these, you will have the option to use print the results in the console or not. If you choose the console option, the results will be printed in the console. If you choose the non-console option or a console is not available in your browser, it will append the results to the DOM.
If you would like to build some of the browser tests individually, the following commands are available:
npm run build:test:browser:es3
: Build the test files for ES3npm run build:test:browser:es3:watch
: Build the test files for ES3 in watch modenpm run build:test:browser:es5
: Build the test files for ES5npm run build:test:browser:es5:watch
: Build the test files for ES5 in watch modenpm run build:test:browser:es6
: Build the test files for ES6npm run build:test:browser:es6:watch
: Build the test files for ES6 in watch modenpm run build:test:browser:esnext
: Build the test files for esnextnpm run build:test:browser:esnext:watch
: Build the test files for esnext in watch modenpm run build:test:browser
: Execute all of the build commands concurrentlynpm run build:test:browser:watch
: Execute all of the build commands concurrently in watch modenpm run build:test
: Same asnpm run build:test:browser
npm run build:test:watch
: Same asnpm run build:test:browser:watch
MIT (see ./LICENSE).