├── .gitignore ├── LICENSE ├── README.md ├── errors ├── Err_call.js ├── Err_constr.js ├── Err_create.js ├── Err_extends.js ├── Err_inherits.js ├── Err_new.js ├── Err_new_setProto.js └── Err_setProto.js ├── index.js ├── lib └── helper.js ├── package.json ├── result.png ├── template.html ├── test.js └── the-one └── CustomError.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /backup 3 | tests.html 4 | NOTES.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018, Onur Yıldırım . All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Custom Errors in JavaScript 2 | 3 | Custom errors are useful when you want to track code with context-specific information. There are several ways to define a custom error in JavaScript. But since inheritance is not a first-citizen feature in JS (until ES2015); there's also so much discussion on how to implement a proper custom error. 4 | 5 | See this [SO Question][so-question], [this][so-answer] and [this][so-answer-2] answers, that leads to [this discussion][gist-discuss] (with [@mbrowne][mbrowne]) and finally, to this module you're viewing. 6 | 7 | This module tests various implementations for custom errors, widely used by developers; such as methods making use of `Error.call()`, `Object.create()`, `new Error()`, `Object.setPrototypeOf()`, `extends` (ES2015) and `util.inherits` (Node). See `/errors` directory for [included implementations][errors-dir]. 8 | 9 | What we expect from a custom error implementation is: 10 | - `e.constructor.name` MUST be set to custom error name. 11 | - `E.prototype` MUST inherit from `Error.prototype`. 12 | - `e.message` MUST be set. 13 | - `e` MUST be an instance of `Error`. 14 | - `e` MUST be an instance of `CustomError`. 15 | - `e.stack` MUST be set and should have line-tracable info. 16 | - `e.tostring()` MUST return error name and/or message. 17 | - `({}).toString.call(e)` SHOULD output `"error"`. 18 | - and some more... 19 | 20 | ### Test 21 | 22 | Run `npm start` which will output test results to console for the current Node.js environment. And will also generate an HTML file, which will display results for the used browser. 23 | 24 | Better, you can [directly view this page][test-page] for testing your current browser. 25 | 26 | [![Screenshot](https://raw.github.com/onury/custom-error-test/master/result.png)][test-page] 27 | _Capture of a test on Chrome 55 browser._ 28 | 29 | ### CustomError: The One 30 | 31 | So; with some research, discussions and these tests I think [**this**][the-one] is the one closest to ES2015's `extends Error`. If you think this is a bit over-kill, [**this**][so-answer] should be quite enough. 32 | 33 | ### License 34 | MIT. 35 | 36 | [test-page]:https://onury.github.io/custom-error-test/ 37 | [errors-dir]:https://github.com/onury/custom-error-test/tree/master/errors 38 | [getPrototypeOf]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf 39 | [setPrototypeOf]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf 40 | [Error]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error 41 | [capturestacktrace_constr]:https://nodejs.org/api/errors.html#errors_error_capturestacktrace_targetobject_constructoropt 42 | [proto]:https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/proto 43 | [so-question]:http://stackoverflow.com/q/1382107/112731 44 | [so-answer]:http://stackoverflow.com/a/35881508/112731 45 | [so-answer-2]:http://stackoverflow.com/a/41338601/112731 46 | [gist-discuss]:https://gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 47 | [the-one]:https://github.com/onury/custom-error-test/tree/master/the-one/CustomError.js 48 | [mbrowne]:https://github.com/mbrowne -------------------------------------------------------------------------------- /errors/Err_call.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function Err_call(message) { 5 | let err = Error.call(this, message || ''); 6 | Object.defineProperty(err, 'name', { 7 | enumerable: false, 8 | writable: false, 9 | value: 'Err_call' 10 | }); 11 | 12 | if (Error.hasOwnProperty('captureStackTrace')) { // V8 13 | Error.captureStackTrace(err, Err_call); 14 | } else { 15 | Object.defineProperty(err, 'stack', { 16 | enumerable: false, 17 | writable: false, 18 | value: (new Error(message)).stack 19 | }); 20 | } 21 | return err; 22 | } 23 | 24 | Err_call.prototype = Object.create(Error.prototype, { 25 | constructor: { value: Err_call } // this makes the difference (compared to Error_create) 26 | }); 27 | // Another way to write this: 28 | // Err_call.prototype = Object.create(Error.prototype); 29 | // Err_call.prototype.constructor = Err_call; 30 | 31 | // EXPORT 32 | 33 | if (typeof module === 'object' && typeof module.exports === 'object') { 34 | module.exports = Err_call; 35 | } else { 36 | window.errors = window.errors || []; 37 | window.errors.push(Err_call); 38 | } 39 | 40 | })(); -------------------------------------------------------------------------------- /errors/Err_constr.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function Err_constr(message) { 5 | Object.defineProperty(this, 'name', { 6 | enumerable: false, 7 | writable: false, 8 | value: 'Err_constr' 9 | }); 10 | 11 | Object.defineProperty(this, 'message', { 12 | enumerable: false, 13 | writable: true, 14 | value: message || '' 15 | }); 16 | 17 | if (Error.hasOwnProperty('captureStackTrace')) { // V8 18 | Error.captureStackTrace(this, Err_constr); 19 | } else { 20 | Object.defineProperty(this, 'stack', { 21 | enumerable: false, 22 | writable: false, 23 | value: (new Error(message)).stack 24 | }); 25 | } 26 | } 27 | 28 | Err_constr.prototype = Object.create(Error.prototype, { 29 | constructor: { value: Err_constr } // this makes the difference (compared to Error_create) 30 | }); 31 | // Another way to write this: 32 | // Err_constr.prototype = Object.create(Error.prototype); 33 | // Err_constr.prototype.constructor = Err_constr; 34 | 35 | // EXPORT 36 | 37 | if (typeof module === 'object' && typeof module.exports === 'object') { 38 | module.exports = Err_constr; 39 | } else { 40 | window.errors = window.errors || []; 41 | window.errors.push(Err_constr); 42 | } 43 | 44 | })(); -------------------------------------------------------------------------------- /errors/Err_create.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function Err_create(message) { 5 | Object.defineProperty(this, 'name', { 6 | enumerable: false, 7 | writable: false, 8 | value: 'Err_create' 9 | }); 10 | 11 | Object.defineProperty(this, 'message', { 12 | enumerable: false, 13 | writable: true, 14 | value: message || '' 15 | }); 16 | 17 | if (Error.hasOwnProperty('captureStackTrace')) { // V8 18 | Error.captureStackTrace(this, Err_create); 19 | } else { 20 | Object.defineProperty(this, 'stack', { 21 | enumerable: false, 22 | writable: false, 23 | value: (new Error(message)).stack 24 | }); 25 | } 26 | } 27 | 28 | Err_create.prototype = Object.create(Error.prototype); 29 | 30 | // EXPORT 31 | 32 | if (typeof module === 'object' && typeof module.exports === 'object') { 33 | module.exports = Err_create; 34 | } else { 35 | window.errors = window.errors || []; 36 | window.errors.push(Err_create); 37 | } 38 | 39 | })(); -------------------------------------------------------------------------------- /errors/Err_extends.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var Err_extends; 5 | 6 | function check() { 7 | if (!Err_extends) { 8 | Err_extends = {}; 9 | // mark as not-supported 10 | Err_extends.noSupport = true; 11 | // set the name so we'll know 12 | Err_extends.name = 'Err_extends'; 13 | } 14 | } 15 | 16 | try { 17 | // In Firefox, eval code below won't work if not put in parenthesis!!! 18 | Err_extends = eval( 19 | "(class Err_extends extends Error {" 20 | + "constructor(message = '') {" 21 | + " super(message);" 22 | + " Object.defineProperty(this, 'name', {" 23 | + " enumerable: false," 24 | + " writable: false," 25 | + " value: this.constructor.name" 26 | + " });" 27 | + "}" 28 | + "})" 29 | ); 30 | } catch (e) { // throws syntax error if not supported 31 | // this block is not executed in Firefox if there is an exception 32 | } 33 | 34 | // so we check here 35 | check(); 36 | 37 | // EXPORT 38 | 39 | if (typeof module === 'object' && typeof module.exports === 'object') { 40 | module.exports = Err_extends; 41 | } else { 42 | window.errors = window.errors || []; 43 | window.errors.push(Err_extends); 44 | } 45 | 46 | })(); -------------------------------------------------------------------------------- /errors/Err_inherits.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var isNode = typeof module === 'object' && typeof module.exports === 'object'; 5 | 6 | function Err_inherits(message) { 7 | Object.defineProperty(this, 'name', { 8 | enumerable: false, 9 | writable: false, 10 | value: 'Err_inherits' 11 | }); 12 | 13 | Object.defineProperty(this, 'message', { 14 | enumerable: false, 15 | writable: true, 16 | value: message || '' 17 | }); 18 | 19 | if (Error.hasOwnProperty('captureStackTrace')) { // V8 20 | Error.captureStackTrace(this, Err_inherits); 21 | } else { 22 | Object.defineProperty(this, 'stack', { 23 | enumerable: false, 24 | writable: false, 25 | value: (new Error(message)).stack 26 | }); 27 | } 28 | } 29 | 30 | if (isNode) { 31 | let util = require('util'); 32 | util.inherits(Err_inherits, Error); 33 | } else { 34 | Err_inherits.noSupport = true; 35 | } 36 | 37 | // EXPORT 38 | 39 | if (isNode) { 40 | module.exports = Err_inherits; 41 | } else { 42 | window.errors = window.errors || []; 43 | window.errors.push(Err_inherits); 44 | } 45 | 46 | })(); -------------------------------------------------------------------------------- /errors/Err_new.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function Err_new(message) { 5 | Object.defineProperty(this, 'name', { 6 | enumerable: false, 7 | writable: false, 8 | value: 'Err_new' 9 | }); 10 | 11 | Object.defineProperty(this, 'message', { 12 | enumerable: false, 13 | writable: true, 14 | value: message || '' 15 | }); 16 | 17 | if (Error.hasOwnProperty('captureStackTrace')) { // V8 18 | Error.captureStackTrace(this, Err_new); 19 | } else { 20 | Object.defineProperty(this, 'stack', { 21 | enumerable: false, 22 | writable: false, 23 | value: (new Error(message)).stack 24 | }); 25 | } 26 | } 27 | Err_new.prototype = new Error(); 28 | 29 | // EXPORT 30 | 31 | if (typeof module === 'object' && typeof module.exports === 'object') { 32 | module.exports = Err_new; 33 | } else { 34 | window.errors = window.errors || []; 35 | window.errors.push(Err_new); 36 | } 37 | 38 | })(); -------------------------------------------------------------------------------- /errors/Err_new_setProto.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var setProto = Object.setPrototypeOf, 5 | Err_new_setProto = {}; 6 | 7 | if (!setProto) { 8 | Err_new_setProto.noSupport = true; 9 | } else { 10 | Err_new_setProto = function Err_new_setProto(message) { 11 | var err = new Error(message); 12 | setProto(err, Err_new_setProto.prototype); 13 | 14 | Object.defineProperty(err, 'name', { 15 | enumerable: false, 16 | writable: false, 17 | value: 'Err_new_setProto' 18 | }); 19 | return err; 20 | } 21 | setProto(Err_new_setProto.prototype, Error.prototype); 22 | } 23 | 24 | // EXPORT 25 | 26 | if (typeof module === 'object' && typeof module.exports === 'object') { 27 | module.exports = Err_new_setProto; 28 | } else { 29 | window.errors = window.errors || []; 30 | window.errors.push(Err_new_setProto); 31 | } 32 | 33 | })(); -------------------------------------------------------------------------------- /errors/Err_setProto.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function Err_setProto(message) { 5 | Object.defineProperty(this, 'name', { 6 | enumerable: false, 7 | writable: false, 8 | value: 'Err_setProto' 9 | }); 10 | 11 | Object.defineProperty(this, 'message', { 12 | enumerable: false, 13 | writable: true, 14 | value: message || '' 15 | }); 16 | 17 | if (Error.hasOwnProperty('captureStackTrace')) { // V8 18 | Error.captureStackTrace(this, Err_setProto); 19 | } else { 20 | Object.defineProperty(this, 'stack', { 21 | enumerable: false, 22 | writable: false, 23 | value: (new Error(message)).stack 24 | }); 25 | } 26 | } 27 | if (typeof Object.setPrototypeOf === 'function') { 28 | Object.setPrototypeOf(Err_setProto.prototype, Error.prototype); 29 | } else { 30 | Err_setProto.noSupport = true; 31 | } 32 | 33 | // EXPORT 34 | 35 | if (typeof module === 'object' && typeof module.exports === 'object') { 36 | module.exports = Err_setProto; 37 | } else { 38 | window.errors = window.errors || []; 39 | window.errors.push(Err_setProto); 40 | } 41 | 42 | })(); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const table = require('text-table'); 4 | const helper = require('./lib/helper'); 5 | const Test = require('./test'); 6 | 7 | (() => { 8 | 'use strict'; 9 | 10 | // import/require all custom errors from ./errors directory 11 | const errors = []; 12 | const errFileLinks = []; 13 | const dir = path.join(__dirname, 'errors'); 14 | fs.readdirSync(dir).forEach(file => { 15 | errors.push(require(path.join(dir, file))); 16 | errFileLinks.push(' '); 17 | }); 18 | 19 | // generate test.html file with errFileLinks using tempate.html 20 | let content = fs.readFileSync(path.join(__dirname, 'template.html'), 'utf8'); 21 | content = content.replace('\n', errFileLinks.join('\n') + '\n'); 22 | fs.writeFileSync(path.join(__dirname, 'tests.html'), content, 'utf8'); 23 | 24 | // log test result for Node env 25 | function testResult() { 26 | const test = new Test(errors, helper); 27 | console.log(); 28 | 29 | console.log('BEGIN —> Stack Samples\n'); 30 | const stackSamples = test.logStackSamples(); 31 | stackSamples.forEach(stack => console.log(stack + '\n')); 32 | console.log('Stack Samples <— END\n'); 33 | console.log(); 34 | 35 | const rows = test.run(); 36 | // center results in each column 37 | let align = (new Array(errors.length + 1)).join('c').split(''); 38 | align.unshift('r'); // test names will be right-aligned 39 | console.log(table(rows, { align })); 40 | console.log(); 41 | console.log('Environment: ', helper.getEnv()); 42 | console.log(); 43 | } 44 | 45 | // --------------------------- 46 | 47 | testResult(); 48 | 49 | })(); 50 | -------------------------------------------------------------------------------- /lib/helper.js: -------------------------------------------------------------------------------- 1 | // Not using arrow functions since this will also be used in the browser. 2 | // http://caniuse.com/#feat=arrow-functions 3 | 4 | var helper = (function () { 5 | 'use strict'; 6 | 7 | var h = {}; 8 | 9 | h.isNode = function () { 10 | return typeof module === 'object' && typeof module.exports === 'object'; 11 | }; 12 | 13 | h.getEnv = function () { 14 | return h.isNode() 15 | ? process.release.name + ' ' + process.version 16 | : navigator.userAgent; 17 | }; 18 | 19 | h.getType = function (o) { 20 | return ({}).toString.call(o).match(/\s(\w+)/i)[1].toLowerCase(); 21 | }; 22 | 23 | h.getTester = function (errors, instances) { 24 | return function test(testName, fn, okIfFails) { 25 | return [testName].concat(errors.map(function (E, index) { 26 | if (!E || E.noSupport) return 'NO SUPPORT'; 27 | // create an instance for the current custom error 28 | var e = instances[index]; 29 | // get a stack sample 30 | var stack; 31 | try { 32 | throw e; 33 | } catch (err) { 34 | stack = err.stack || ''; 35 | } 36 | // execute the callback 37 | var val = fn(E, e, stack); 38 | // display */no/FAIL if result is boolean, otherwise display string. 39 | var f = okIfFails ? 'no' : 'FAIL'; 40 | return typeof val === 'boolean' 41 | ? (val ? '*' : f) 42 | : '"' + String(val) + '"'; 43 | })); 44 | }; 45 | } 46 | 47 | return h; 48 | 49 | })(); 50 | 51 | if (helper.isNode()) { 52 | module.exports = helper; 53 | } else { 54 | window.helper = helper; 55 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-error-test", 3 | "version": "1.0.0", 4 | "description": "Compare and test various custom error implementations.", 5 | "main": "index.js", 6 | "files": [ 7 | "errors", 8 | "lib", 9 | "template.html", 10 | "index.js", 11 | "test.js", 12 | "LICENSE" 13 | ], 14 | "scripts": { 15 | "start": "node ./index", 16 | "test": "echo \"Error: no test specified\" && exit 1" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/onury/custom-error-test.git" 21 | }, 22 | "keywords": [ 23 | "custom", 24 | "error", 25 | "constructor", 26 | "extends", 27 | "setprototypeof", 28 | "prototype", 29 | "create", 30 | "object", 31 | "proto", 32 | "__proto__" 33 | ], 34 | "author": "Onur Yildirim", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/onury/custom-error-test/issues" 38 | }, 39 | "homepage": "https://github.com/onury/custom-error-test#readme", 40 | "dependencies": { 41 | "text-table": "^0.2.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onury/custom-error-test/a22032a7227ea3f50aa140cc7f7ea5bdfdbd45bf/result.png -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 57 | 58 | 153 | 154 | 155 | 156 | 157 |

Custom Error Test

158 |
159 | View on GitHub 160 |
161 | 162 |
163 |
164 |
165 |
166 | 167 | Notes: 168 | 176 |
177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | // Not using arrow functions since this will also be used in the browser. 2 | // http://caniuse.com/#feat=arrow-functions 3 | 4 | (function () { 5 | 'use strict'; 6 | 7 | function Test(errs, helper) { 8 | this.helper = helper; 9 | // add `Error` so we can compare 10 | this.errors = errs.concat(); 11 | this.errors.push(Error); 12 | this.ERR_MSG = 'err message'; 13 | 14 | var self = this; 15 | // create instances for each custom error and Error 16 | this.instances = this.errors.map(function (Err) { 17 | try { 18 | return new Err(self.ERR_MSG); 19 | } catch (e) { 20 | return null; 21 | } 22 | }); 23 | // get names for display purposes 24 | this.names = this.instances.map(function (e, index) { 25 | // if this custom error is not supported in the env, `e` will be 26 | // `null`. then we'll get the name from the object's name 27 | // property which is explicitly set for this purpose. 28 | return e ? e.name : (self.errors[index] ? self.errors[index].name : '-'); 29 | }); 30 | this.exec = helper.getTester(this.errors, this.instances); 31 | } 32 | 33 | Test.prototype.run = function () { 34 | var self = this; 35 | return [ 36 | // first row is columns 37 | [''].concat(this.names), 38 | 39 | this.exec('e.name', function (E, e, s) { return e.name; }), 40 | this.exec('e.constructor.name', function (E, e, s) { return e.constructor.name; }), 41 | // this.exec('E.constructor.name', function (E, e, s) { return E.constructor.name; }), 42 | this.exec('e.constructor.name === e.name', function (E, e, s) { return e.constructor.name === e.name; }), 43 | this.exec('e.message === ERR_MSG', function (E, e, s) { return e.message === self.ERR_MSG; }), 44 | this.exec('typeof e === "object"', function (E, e, s) { return typeof e === 'object'; }), 45 | this.exec('typeof E === "function"', function (E, e, s) { return typeof E === 'function'; }), 46 | this.exec('getType(e) === "error"', function (E, e, s) { return self.helper.getType(e) === 'error'; }, true), 47 | this.exec('getType(E) === "function"', function (E, e, s) { return self.helper.getType(E) === 'function'; }), 48 | // this.exec('getPrototypeOf(E) === Function.prototype', function (E, e, s) { return Object.getPrototypeOf(E) === Function.prototype; }), 49 | // this.exec('getPrototypeOf(E) === Error.prototype', function (E, e, s) { return Object.getPrototypeOf(E) === Error.prototype; }), 50 | this.exec('getPrototypeOf(e) === Error.prototype', function (E, e, s) { return Object.getPrototypeOf(e) === Error.prototype; }, true), 51 | this.exec('getPrototypeOf(e) === E.prototype', function (E, e, s) { return Object.getPrototypeOf(e) === E.prototype; }), 52 | // this.exec('getPrototypeOf(E) === E.prototype', function (E, e, s) { return Object.getPrototypeOf(E) === E.prototype; }), 53 | // this.exec('getPrototypeOf(E) === getPrototypeOf(e)', function (E, e, s) { return Object.getPrototypeOf(E) === Object.getPrototypeOf(e); }), 54 | this.exec('e instanceof E', function (E, e, s) { return e instanceof E; }), 55 | this.exec('e instanceof Error', function (E, e, s) { return e instanceof Error; }), 56 | this.exec('e.toString()', function (E, e, s) { return typeof e.toString === 'function' && e.toString().indexOf(e.name) >= 0; }), 57 | this.exec('e.stack has name', function (E, e, s) { return s.indexOf(e.name) >= 0; }), 58 | this.exec('e.stack has ERR_MSG', function (E, e, s) { return s.indexOf(self.ERR_MSG) >= 0; }, true), 59 | this.exec('e.stack has line info', function (E, e, s) { return s.indexOf('/test.js:') >= 0; }), 60 | this.exec('e.stack has no extra line', function (E, e, s) { var sp = s.split('\n'); return sp.length > 1 && sp[1].indexOf(e.name) < 0; }, true), 61 | this.exec('getPrototypeOf(E) === E.__proto__', function (E, e, s) { return Error.__proto__ && Object.getPrototypeOf(E) === E.__proto__; }, true), 62 | this.exec('getPrototypeOf(e) === e.__proto__', function (E, e, s) { return Error.__proto__ && Object.getPrototypeOf(e) === e.__proto__; }, true) 63 | ]; 64 | }; 65 | 66 | Test.prototype.logStackSamples = function () { 67 | return this.instances.map(function (e) { 68 | if (e === null) return 'NO SUPPORT'; 69 | var stack = (e.constructor ? e.constructor.name : '(unknown)') + ' stack sample:\n'; 70 | try { 71 | throw e; 72 | } catch (err) { 73 | stack += err.stack; 74 | } 75 | return stack; 76 | }); 77 | }; 78 | 79 | if (typeof module === 'object' && typeof module.exports === 'object') { 80 | module.exports = Test; 81 | } else { 82 | window.Test = Test; 83 | } 84 | 85 | })(); 86 | 87 | -------------------------------------------------------------------------------- /the-one/CustomError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JS CustomError implementation — The One 3 | * This is the closest we can get to ES2015 `extends Error` implementation. 4 | * @version 2017-01-05 5 | * @author 6 | * Onur Yıldırım (https://github.com/onury) 7 | * Matt Browne (https://github.com/mbrowne) 8 | * @see 9 | * https://github.com/onury/custom-error-test 10 | * http://stackoverflow.com/a/35881508/112731 11 | * https://gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 12 | * http://stackoverflow.com/a/41338601/112731 13 | * 14 | */ 15 | (function () { 16 | 'use strict'; 17 | 18 | var setProto = Object.setPrototypeOf; 19 | 20 | function CustomError(message) { 21 | var err; 22 | if (setProto) { 23 | err = new Error(message); 24 | setProto(err, CustomError.prototype); 25 | } else { 26 | err = this; 27 | } 28 | 29 | Object.defineProperty(err, 'name', { 30 | enumerable: false, 31 | writable: false, 32 | value: 'CustomError' 33 | }); 34 | 35 | if (!setProto) { 36 | Object.defineProperty(err, 'message', { 37 | enumerable: false, 38 | writable: true, 39 | value: message 40 | }); 41 | if (Error.captureStackTrace) { 42 | Error.captureStackTrace(err, CustomError); 43 | } else { 44 | Object.defineProperty(err, 'stack', { 45 | enumerable: false, 46 | writable: false, 47 | value: (new Error(message)).stack 48 | }); 49 | } 50 | } 51 | 52 | return err; 53 | } 54 | if (setProto) { 55 | setProto(CustomError.prototype, Error.prototype); 56 | } else { 57 | CustomError.prototype = Object.create(Error.prototype, { 58 | constructor: { value: CustomError } 59 | }); 60 | } 61 | 62 | })(); 63 | --------------------------------------------------------------------------------