Skip to content

Commit 7c31cdb

Browse files
author
Mat Jones
committed
add ability to configure global error handling for Do.try
1 parent bda6697 commit 7c31cdb

3 files changed

Lines changed: 132 additions & 3 deletions

File tree

src/types/do-try-types.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,28 @@ type SyncWorkload<T> = () => T;
99
type CatchHandler<T> = (result?: ResultRecord<T>, error?: any) => void;
1010
type FinallyHandler = () => void;
1111

12+
interface DoTryConfig {
13+
/**
14+
* A default handler that will always run on error, if configured,
15+
* even if a `catch()` does not exist in the call chain.
16+
* This is useful for adding default error handling in the
17+
* development environment, such as `console.error(err)`.
18+
*/
19+
defaultErrorHandler?: CatchHandler<any>;
20+
}
21+
1222
// #endregion Types
1323

1424
// -----------------------------------------------------------------------------------------
1525
// #region Exports
1626
// -----------------------------------------------------------------------------------------
1727

18-
export { AsyncWorkload, SyncWorkload, CatchHandler, FinallyHandler };
28+
export {
29+
AsyncWorkload,
30+
CatchHandler,
31+
DoTryConfig,
32+
FinallyHandler,
33+
SyncWorkload,
34+
};
1935

2036
// #endregion Exports

src/utilities/do-try.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Do, DoSync } from "../utilities/do-try";
88
import { PolyfillUtils } from "../utilities/polyfill-utils";
99
import { StubResourceRecord } from "../tests/stubs/stub-resource-record";
1010
import { CoreUtils } from "../utilities/core-utils";
11+
import { CatchHandler } from "../types/do-try-types";
1112

1213
PolyfillUtils.registerPromiseFinallyPolyfill();
1314

@@ -119,6 +120,42 @@ describe("do-try.ts", () => {
119120
});
120121
});
121122

123+
describe("Do.configure", () => {
124+
it("When defaultErrorHandler configured and no catch() in call chain, then defaultErrorHandler is still executed", async () => {
125+
// Arrange
126+
const defaultErrorHandler: CatchHandler<any> = jest.fn();
127+
Do.configure({ defaultErrorHandler });
128+
const catchHandler = jest.fn();
129+
const workload = async () => {
130+
throw Error();
131+
};
132+
133+
// Act & Assert
134+
Do.try(workload)
135+
.catch(catchHandler)
136+
.finally(() => {
137+
expect(catchHandler).toHaveBeenCalled();
138+
expect(defaultErrorHandler).toHaveBeenCalled();
139+
});
140+
expect.assertions(2);
141+
});
142+
143+
it("When defaultErrorHandler configured and catch() is in call chain, the defaultErrorHandler is still executed", async () => {
144+
// Arrange
145+
const defaultErrorHandler: CatchHandler<any> = jest.fn();
146+
Do.configure({ defaultErrorHandler });
147+
const workload = async () => {
148+
throw Error();
149+
};
150+
151+
// Act & Assert
152+
Do.try(workload).finally(() => {
153+
expect(defaultErrorHandler).toHaveBeenCalled();
154+
});
155+
expect.assertions(1);
156+
});
157+
});
158+
122159
describe("DoSync.try", () => {
123160
it("When no errors occur, then .execute() returns the workload return value", () => {
124161
// Arrange
@@ -204,4 +241,40 @@ describe("do-try.ts", () => {
204241
expect(finallyHandler).toHaveBeenCalled();
205242
});
206243
});
244+
245+
describe("DoSync.configure", () => {
246+
it("When defaultErrorHandler configured and no catch() in call chain, then defaultErrorHandler is still executed", () => {
247+
// Arrange
248+
const defaultErrorHandler: CatchHandler<any> = jest.fn();
249+
DoSync.configure({ defaultErrorHandler });
250+
const catchHandler = jest.fn();
251+
const workload = () => {
252+
throw Error();
253+
};
254+
255+
// Act
256+
DoSync.try(workload)
257+
.catch(catchHandler)
258+
.execute();
259+
260+
// Assert
261+
expect(catchHandler).toHaveBeenCalled();
262+
expect(defaultErrorHandler).toHaveBeenCalled();
263+
});
264+
265+
it("When defaultErrorHandler configured and catch() is in call chain, the defaultErrorHandler is still executed", () => {
266+
// Arrange
267+
const defaultErrorHandler: CatchHandler<any> = jest.fn();
268+
DoSync.configure({ defaultErrorHandler });
269+
const workload = () => {
270+
throw Error();
271+
};
272+
273+
// Act
274+
DoSync.try(workload).execute();
275+
276+
// Assert
277+
expect(defaultErrorHandler).toHaveBeenCalled();
278+
});
279+
});
207280
});

src/utilities/do-try.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { ResultRecord } from "../view-models/result-record";
22
import {
33
AsyncWorkload,
4-
SyncWorkload,
54
CatchHandler,
5+
DoTryConfig,
66
FinallyHandler,
7+
SyncWorkload,
78
} from "../types/do-try-types";
89

910
// -----------------------------------------------------------------------------------------
@@ -19,8 +20,20 @@ import {
1920
class Do<TResourceType, TReturnVal = void> {
2021
private promise: Promise<TReturnVal>;
2122

23+
private static config: DoTryConfig = {
24+
defaultErrorHandler: undefined,
25+
};
26+
2227
private constructor(workload: AsyncWorkload<TReturnVal>) {
23-
this.promise = workload();
28+
this.promise = workload().catch((err: any) => {
29+
if (err instanceof ResultRecord) {
30+
Do.config.defaultErrorHandler?.(err, undefined);
31+
throw err; // rethrow so it doesn't interrupt call chain
32+
}
33+
34+
Do.config.defaultErrorHandler?.(undefined, err);
35+
throw err; // rethrow so it doesn't interrupt call chain
36+
});
2437
}
2538

2639
/**
@@ -47,6 +60,14 @@ class Do<TResourceType, TReturnVal = void> {
4760
return this;
4861
}
4962

63+
/**
64+
* Sets the global configuration object for class {Do}
65+
* @param config the {DoTryConfig} object to set
66+
*/
67+
public static configure(config: DoTryConfig): void {
68+
Do.config = config;
69+
}
70+
5071
/**
5172
* Run some handler when the function completes, whether the
5273
* catch() was hit or not.
@@ -96,6 +117,10 @@ class DoSync<TResourceType, TReturnVal = void> {
96117
private catchHandler?: (err: any) => void;
97118
private finallyHandler?: FinallyHandler;
98119

120+
private static config: DoTryConfig = {
121+
defaultErrorHandler: undefined,
122+
};
123+
99124
private constructor(workload: SyncWorkload<TReturnVal>) {
100125
this.workload = workload;
101126
}
@@ -123,6 +148,14 @@ class DoSync<TResourceType, TReturnVal = void> {
123148
return this;
124149
}
125150

151+
/**
152+
* Sets the global configuration for class {DySync}.
153+
* @param config the {DoTryConfig} object to set
154+
*/
155+
public static configure(config: DoTryConfig): void {
156+
DoSync.config = config;
157+
}
158+
126159
/**
127160
* Execute the entire DoSync call chain. For the synchronous version, i.e. DoSync,
128161
* you must manually call .execute() for the call chain to be executed.
@@ -132,6 +165,13 @@ class DoSync<TResourceType, TReturnVal = void> {
132165
try {
133166
return this.workload();
134167
} catch (e) {
168+
if (e instanceof ResultRecord) {
169+
DoSync.config.defaultErrorHandler?.(e, undefined);
170+
this.catchHandler?.(e);
171+
return;
172+
}
173+
174+
DoSync.config.defaultErrorHandler?.(undefined, e);
135175
this.catchHandler?.(e);
136176
} finally {
137177
this.finallyHandler?.();

0 commit comments

Comments
 (0)