-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathnested-error.ts
120 lines (111 loc) · 4.64 KB
/
nested-error.ts
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
/**
* Subclass of standard `Error` that eagerly collects the callstack of the error
* that caused it. This way you can investigate the core problem that happened
* by looking at the callstack from up to bottom (from higher level errors to
* lower level).
*/
export class NestedError extends Error {
/**
* Combined callstack of this error and the errors that it wraps.
* If the JavaScript runtime doesn't support `Error::stack` property
* this will contain only the concatenated messages.
*/
readonly stack: string;
/**
* The list of lower-level errors wrapped by this error.
*/
readonly innerErrors: Error[];
/**
* Provides the first `Error` of the `innerErrors` (if it exists);
* otherwise, `null`.
*
* @deprecated Please shift to using the `innerErrors` (with an 's') property.
*/
get innerError(): Error | null {
return this.innerErrors.length === 0
? null
: this.innerErrors[0];
}
private static readonly getErrorReport = typeof new Error().stack === 'string'
? (err: Error) => err.stack!
: (err: Error) => `${err.name}: ${err.message}`;
/**
* Returns the function that accepts any value that was thrown as the first argument and
* throws it wrapped into `NestedError` or class derived from `NestedError` (provided
* this method was called directly in the context of that dervied class constructor)
* with the given `message`.
* Returned function will pass accepted `Error` object directly to `NestedError`
* as `innerErrors` by invoking `toError(err)` on it.
*
* You'll most likely want to use this method with promises:
*
* ```ts
* userService.getPage().then(
* data => console.log(`Hooray! data: ${data}`),
* NestedError.rethrow('failed to fetch users page')
* );
* ```
*
* @param message Message to attach `NestedError` created by the returned function.
*/
static rethrow(message: string) {
return (...errs: unknown[]) => { throw new this(message, ...errs); };
}
/**
* Allocates an instance of `NestedError` with the given error `message` and
* optional `innerError` (which will be automatically coerced using `toError()`).
*
* @param message Laconic error message to attach to the created `NestedError`.
* @param innerErrors Optional errors that will be wrapped by this higher level
* error. This value will be automatically coerced using `toError()`.
*/
constructor(message?: string, ...innerErrors: unknown[]) {
super(message);
const thisErrorReport = NestedError.getErrorReport(this);
if (innerErrors.length === 1) {
const innerError = toError(innerErrors[0]);
this.innerErrors = [innerError];
const errReport = NestedError.getErrorReport(innerError);
this.stack = `${thisErrorReport}\n\n======= INNER ERROR =======\n\n${errReport}`;
return;
}
this.innerErrors = innerErrors.map(err => toError(err));
const innerErrorReports = this.innerErrors
.map((error, idx) => {
const errReport = NestedError.getErrorReport(error);
return `======= INNER ERROR (${idx + 1} of ${innerErrors.length}) =======\n\n${errReport}`;
})
.join("\n\n");
this.stack = `${thisErrorReport}\n\n${innerErrorReports}`;
}
}
NestedError.prototype.name = nameof(NestedError);
/**
* @deprecated You should not call this function on an object of statically assumed `Error` type,
* because it is intended to be used in a dynamic context where the type of thrown value
* is not known ahead of time (during the compile time).
*/
export function toError(err: Error): Error;
/**
* Returns `err` itself if `err instanceof Error === true`, otherwise attemts to
* stringify it and wrap into `Error` object to be returned.
*
* **This function is guaranteed never to throw.**
*
* @param err Possbile `instanceof Error` to return or value of any type that will
* be wrapped into a fully-fledged `Error` object.
*/
export function toError(err: unknown): Error;
export function toError(err: unknown) {
try {
return err instanceof Error
? err
: new Error(`Value that is not an instance of Error was thrown: ${err}`);
} catch {
return new Error(
"Failed to stringify non-instance of Error that was thrown." +
"This is possibly due to the fact that toString() method of the value" +
"doesn't return a primitive value."
);
}
}