-
Notifications
You must be signed in to change notification settings - Fork 4
/
error.js
141 lines (141 loc) · 5.61 KB
/
error.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
"use strict";
var util = require('util');
/**
* Create a custom error generator
*
* @param {String} name The error name
* @param {Object} parameters An optional set of key=value pairs to attach to errors
* @param {Function} Constructor An optional parent Error class to inherit from
* @return {Function}
*/
module.exports = function createError (name, parameters, Constructor) {
if (!name) {
throw new TypeError('A custom error name is required');
}
// Check to make sure we're inheriting from a regular function first
if (Constructor && typeof Constructor == 'function') {
// If we're not starting from the Error prototype, add the Error prototype
// to the Constructor prototype chain
if (!Error.prototype.isPrototypeOf(Constructor.prototype)) {
var parentProto = { };
Object.getOwnPropertyNames(Constructor.prototype).forEach(function (name) {
parentProto[name] = Object.getOwnPropertyDescriptor(Constructor.prototype, name);
});
Constructor.prototype = Object.create(Error.prototype, parentProto);
}
// Otherwise simply inherit from the Error built-in
} else {
Constructor = Error;
}
// If we're given additional parameters, attach them to the created object
var properties = {};
if (parameters) {
Object.keys(parameters).forEach(function (property) {
properties[property] = {
'value' : parameters[property],
'enumerable' : true,
'writable' : true,
'configurable' : true
};
});
}
// Set up the custom properties for this Error object, if specified.
// Create a new stack descriptor that includes the stacks for any errors
// also passed in
function createStackDescriptor (errors, previous) {
return function () {
var stack = previous.get();
errors.forEach(function (error) {
stack += '\n';
stack += error.stack;
});
return stack;
};
}
// The custom error function that's returned. Since it always creates a new
// exception, we don't have to worry if itself was invoked with a new
// operator or not.
function CustomError (message) {
// We start by simply creating a new error object, so that we preserve
// the runtime error logic
var proxy = new Error(message);
// We want to call our constructor on the error object itself,
Constructor.apply(this, arguments);
// We also want to preserve the inheritance logic in the prototype chain
Constructor.apply(proxy, arguments);
// Capture the stack trace at the appropriate stack location
Error.captureStackTrace(proxy, CustomError);
// Make a backup of our existing stack descriptor
var stackDescriptor = Object.getOwnPropertyDescriptor(proxy, 'stack');
// We now loop through all of the arguments to collect any sub errors,
// And to allow for custom string formatting with the message
var errors = [];
var messages = [];
var length = arguments.length;
var index = length;
while (index--) {
var param = arguments[length - index - 1];
if (param instanceof Error) {
errors.push(param);
} else if (typeof param == 'string' || typeof param == 'number') {
messages.push(param);
}
}
// If we have any errors that were passed in, replace the stack
// descriptor that includes the other error stacks
if (errors.length > 0) {
properties.stack = {
'get' : createStackDescriptor(errors, stackDescriptor)
};
}
// Always set the message manually, in case there was a default supplied
// format with util.format
properties.message = {
'value' : util.format.apply(null, messages)
};
// Pass in our extra properties
Object.defineProperties(proxy, properties);
// Replace the error prototype with our own
proxy.__proto__ = Object.create(CustomError.prototype, properties);
return proxy;
}
// Set up the new prototype
var proto = {
'constructor' : {
'value' : CustomError,
'writable' : true,
'configurable' : true
},
'name' : {
'value' : name,
'enumerable' : false,
'writable' : true,
'configurable' : true
},
'toJSON' : {
'enumerable' : false,
'configurable' : false,
'value' : function () {
var json = {};
Object.getOwnPropertyNames(this).forEach(function (name) {
json[name] = name == 'stack' ? this[name].split('\n') : this[name];
}, this);
return json;
}
}
};
// Also set the passed in parameters on the prototype if it's inherited later
if (parameters) {
Object.keys(parameters).forEach(function (name) {
proto[name] = {
'value' : parameters[name],
'enumerable' : true,
'writable' : true,
'configurable' : true
};
});
}
// Copying from the Error prototype allows us to preserve the built-in error checks
CustomError.prototype = Object.create(Constructor.prototype, proto);
return CustomError;
};