Last active
January 25, 2020 13:32
-
-
Save andre487/849338b70f21aafaaa4a to your computer and use it in GitHub Desktop.
Crossbrowser custom error
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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