Skip to content

Commit ca11227

Browse files
Issue #55 Update Do.try to allow configuring a default error handler
add ability to configure global error handling for Do.try
2 parents bda6697 + 4e2abfd commit ca11227

10 files changed

Lines changed: 189 additions & 33 deletions

File tree

docs/interfaces/dotryconfig.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[andculturecode-javascript-core](../README.md)[DoTryConfig](dotryconfig.md)
2+
3+
# Interface: DoTryConfig
4+
5+
## Hierarchy
6+
7+
* **DoTryConfig**
8+
9+
## Index
10+
11+
### Properties
12+
13+
* [defaultErrorHandler](dotryconfig.md#optional-defaulterrorhandler)
14+
15+
## Properties
16+
17+
### `Optional` defaultErrorHandler
18+
19+
**defaultErrorHandler**? : *[CatchHandler](../README.md#catchhandler)‹any›*
20+
21+
*Defined in [src/types/do-try-types.ts:19](https://github.com/mrjones2014/AndcultureCode.JavaScript.Core/blob/7c31cdb/src/types/do-try-types.ts#L19)*
22+
23+
A default handler that will always run on error, if configured,
24+
even if a `catch()` does not exist in the call chain.
25+
This is useful for adding default error handling in the
26+
development environment, such as `console.error(err)`.

src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ export { User } from "./interfaces/user";
5454
// #region Types
5555
// -----------------------------------------------------------------------------------------
5656

57-
export { AsyncWorkload } from "./types/do-try-types";
57+
export { AsyncWorkload } from "./types/async-workload";
5858
export { CancellablePromise } from "./types/cancellable-promise";
59-
export { CatchHandler } from "./types/do-try-types";
59+
export { CatchResultHandler } from "./types/catch-result-handler";
6060
export { Constructor } from "./types/constructor";
61-
export { FinallyHandler } from "./types/do-try-types";
62-
export { SyncWorkload } from "./types/do-try-types";
61+
export { FinallyHandler } from "./types/finally-handler";
62+
export { SyncWorkload } from "./types/sync-workload";
6363

6464
// #endregion Types
6565

src/interfaces/do-try-config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { CatchResultHandler } from "../types/catch-result-handler";
2+
3+
interface DoTryConfig {
4+
/**
5+
* A default handler that will always run on error, if configured,
6+
* even if a `catch()` does not exist in the call chain.
7+
* This is useful for adding default error handling in the
8+
* development environment, such as `console.error(err)`.
9+
*/
10+
defaultErrorHandler?: CatchResultHandler<any>;
11+
}
12+
13+
export { DoTryConfig };

src/types/async-workload.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Represents an asynchronous method reference.
3+
*/
4+
type AsyncWorkload<T> = () => Promise<T>;
5+
6+
export { AsyncWorkload };

src/types/catch-result-handler.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ResultRecord } from "../view-models/result-record";
2+
3+
/**
4+
* Handler for a typed error ResultRecord, or any type if a Javascript error occurred.
5+
*/
6+
type CatchResultHandler<T> = (result?: ResultRecord<T>, error?: any) => void;
7+
8+
export { CatchResultHandler };

src/types/do-try-types.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/types/finally-handler.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Handler for Do.try().finally(); Runs whether an error occurred or not.
3+
*/
4+
type FinallyHandler = () => void;
5+
6+
export { FinallyHandler };

src/types/sync-workload.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Represents a synchronous method reference.
3+
*/
4+
type SyncWorkload<T> = () => T;
5+
6+
export { SyncWorkload };

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 { CatchResultHandler } from "../types/catch-result-handler";
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 catch() is in call chain, then defaultErrorHandler is still executed", async () => {
125+
// Arrange
126+
const defaultErrorHandler: CatchResultHandler<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 no catch() is in call chain, the defaultErrorHandler is still executed", async () => {
144+
// Arrange
145+
const defaultErrorHandler: CatchResultHandler<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 catch() in call chain, then defaultErrorHandler is still executed", () => {
247+
// Arrange
248+
const defaultErrorHandler: CatchResultHandler<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 no catch() is in call chain, the defaultErrorHandler is still executed", () => {
266+
// Arrange
267+
const defaultErrorHandler: CatchResultHandler<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: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { ResultRecord } from "../view-models/result-record";
2-
import {
3-
AsyncWorkload,
4-
SyncWorkload,
5-
CatchHandler,
6-
FinallyHandler,
7-
} from "../types/do-try-types";
2+
import { DoTryConfig } from "../interfaces/do-try-config";
3+
import { AsyncWorkload } from "../types/async-workload";
4+
import { CatchResultHandler } from "../types/catch-result-handler";
5+
import { FinallyHandler } from "../types/finally-handler";
6+
import { SyncWorkload } from "../types/sync-workload";
87

98
// -----------------------------------------------------------------------------------------
109
// #region Do
@@ -19,8 +18,20 @@ import {
1918
class Do<TResourceType, TReturnVal = void> {
2019
private promise: Promise<TReturnVal>;
2120

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

2637
/**
@@ -33,7 +44,7 @@ class Do<TResourceType, TReturnVal = void> {
3344
* @returns this
3445
*/
3546
public catch(
36-
errorHandler: CatchHandler<TResourceType>
47+
errorHandler: CatchResultHandler<TResourceType>
3748
): Do<TResourceType, TReturnVal> {
3849
this.promise = this.promise.catch((err: any) => {
3950
if (err instanceof ResultRecord) {
@@ -47,6 +58,14 @@ class Do<TResourceType, TReturnVal = void> {
4758
return this;
4859
}
4960

61+
/**
62+
* Sets the global configuration object for class {Do}
63+
* @param config the {DoTryConfig} object to set
64+
*/
65+
public static configure(config: DoTryConfig): void {
66+
Do.config = config;
67+
}
68+
5069
/**
5170
* Run some handler when the function completes, whether the
5271
* catch() was hit or not.
@@ -96,6 +115,10 @@ class DoSync<TResourceType, TReturnVal = void> {
96115
private catchHandler?: (err: any) => void;
97116
private finallyHandler?: FinallyHandler;
98117

118+
private static config: DoTryConfig = {
119+
defaultErrorHandler: undefined,
120+
};
121+
99122
private constructor(workload: SyncWorkload<TReturnVal>) {
100123
this.workload = workload;
101124
}
@@ -109,7 +132,7 @@ class DoSync<TResourceType, TReturnVal = void> {
109132
* @param errorHandler handle errors, either as a {ResultRecord} or {any}
110133
*/
111134
public catch(
112-
errorHandler: CatchHandler<TResourceType>
135+
errorHandler: CatchResultHandler<TResourceType>
113136
): DoSync<TResourceType, TReturnVal> {
114137
this.catchHandler = (err: any) => {
115138
if (err instanceof ResultRecord) {
@@ -123,6 +146,14 @@ class DoSync<TResourceType, TReturnVal = void> {
123146
return this;
124147
}
125148

149+
/**
150+
* Sets the global configuration for class {DySync}.
151+
* @param config the {DoTryConfig} object to set
152+
*/
153+
public static configure(config: DoTryConfig): void {
154+
DoSync.config = config;
155+
}
156+
126157
/**
127158
* Execute the entire DoSync call chain. For the synchronous version, i.e. DoSync,
128159
* you must manually call .execute() for the call chain to be executed.
@@ -132,6 +163,13 @@ class DoSync<TResourceType, TReturnVal = void> {
132163
try {
133164
return this.workload();
134165
} catch (e) {
166+
if (e instanceof ResultRecord) {
167+
DoSync.config.defaultErrorHandler?.(e, undefined);
168+
this.catchHandler?.(e);
169+
return;
170+
}
171+
172+
DoSync.config.defaultErrorHandler?.(undefined, e);
135173
this.catchHandler?.(e);
136174
} finally {
137175
this.finallyHandler?.();

0 commit comments

Comments
 (0)