Skip to content

Instantly share code, notes, and snippets.

@andre487
Last active January 25, 2020 13:32
Show Gist options
  • Save andre487/849338b70f21aafaaa4a to your computer and use it in GitHub Desktop.
Save andre487/849338b70f21aafaaa4a to your computer and use it in GitHub Desktop.
Crossbrowser custom error
/**
* Universal custom error class
* @param {String} message
* @param {Error} [prevError]
* @constructor
*/
function CustomError(message, prevError) {
if (!CustomError.isInstance(this)) {
// Can't use custom errors like `throw Error('Message')`
// because of stack capturing
throw Error('Invalid CustomError usage');
}
this._isCustomErrorInstance = true;
this.name = 'CustomError';
this.description = this.message = message;
this.stack = '';
this.prevError = prevError;
if (Error.captureStackTrace) {
// V8 specific stack capturing
Error.captureStackTrace(this, this.constructor);
} else {
this._createOwnStack();
}
this.originalStack = this.stack;
this.stack = this.getFullStack();
}
CustomError.prototype = Object.create ? Object.create(Error.prototype) : Error('Prototype');
CustomError.prototype.constructor = CustomError;
/**
* Check err object is a CustomError instance
* @param {Object} err
* @returns {Boolean|null}
*/
CustomError.isInstance = function isInstance(err) {
var isInstance = null;
if (!err || ('__proto__' in err && !err.__proto__)) {
// a falsy or an `obj = Object.create(null)` is passed
isInstance = false;
} else if (err instanceof Object) {
// err created in the same frame
isInstance = err instanceof CustomError;
} else if (typeof err.constructor === 'function' && 'name' in err.constructor) {
// err created in the different frame
isInstance = err.constructor.name === 'CustomError' && err._isCustomErrorInstance;
} else {
// check for the legacy environments, less accuracy
isInstance = err._isCustomErrorInstance;
}
return isInstance;
};
/**
* Get a stack trace of the current error instance
* @returns {String|undefined}
*/
CustomError.prototype.getStack = function getStack() {
return this.originalStack || this.stack;
};
/**
* Get a full trace of the current and all the
* previous instances stacks
* @returns {String|undefined}
*/
CustomError.prototype.getFullStack = function getFullStack() {
var stack = this.getStack(),
prev = this.prevError;
while (prev && prev.stack) {
stack += '\n\n' + prev.stack;
prev = prev.prevError;
}
return stack;
};
CustomError.prototype._createOwnStack = function _createOwnStack() {
this._catchErrorStack();
if (!this.stack) {
this._createStackByCallee();
}
};
CustomError.prototype._catchErrorStack = function _catchErrorStack() {
try {
var err = Error('Stack collector');
err.name = this.name;
err.message = err.description = this.message;
throw err;
} catch (stackCollector) {
this.stack = stackCollector.stack;
this.fileName = stackCollector.fileName;
this.lineNumber = stackCollector.lineNumber;
this.columnNumber = stackCollector.columnNumber;
}
};
CustomError.prototype._createStackByCallee = function _createStackByCallee() {
var caller = arguments.callee && arguments.callee.caller,
nameRegexp = /function\s*(\w+)\s*\(/,
stack = [],
nameData;
while (caller) {
nameData = nameRegexp.exec(caller);
stack.push(nameData ? nameData[1] : '(anonymous)');
caller = caller.caller;
}
this.stack = String(this) + '\n';
for (var i = 0; i < stack.length; ++i) {
this.stack += i + ': ' + stack[i] + '\n';
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment