Skip to content

Commit

Permalink
Feature/error handling (#940)
Browse files Browse the repository at this point in the history
<!--- Provide a general summary of your changes in the Title above
(following the Conventional Commits standard) -->
<!-- More infos: https://www.conventionalcommits.org -->
<!-- Commit types:
https://github.com/insurgent-lab/conventional-changelog-preset#commit-types-->

## Description

<!--- Describe your changes in detail -->

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an
issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps
to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## How Has This Been Tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran
to -->
<!--- see how your change affects other areas of the code, etc. -->

## Screenshots (if appropriate):

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->

- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] If my change introduces a breaking change, I have added a `!`
after the type/scope in the title (see the Conventional Commits
standard).

---------

Co-authored-by: Yoseph <[email protected]>
Co-authored-by: Pierre Cavin <[email protected]>
  • Loading branch information
3 people authored Jan 9, 2025
1 parent e47fd5a commit bcf9282
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 7 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ day of week 0-7 (0 or 7 is Sunday, or use names)

- `waitForCompletion`: [OPTIONAL] - If `true`, no additional instances of the `onTick` callback function will run until the current onTick callback has completed. Any new scheduled executions that occur while the current callback is running will be skipped entirely. Default is `false`.

- `errorHandler`: [OPTIONAL] - Function to handle any exceptions that occur in the `onTick` method.

#### Methods

- `from` (static): Create a new CronJob object providing arguments as an object. See argument names and descriptions above.
Expand Down
24 changes: 17 additions & 7 deletions src/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class CronJob<OC extends CronOnCompleteCommand | null = null, C = null> {
? CronOnCompleteCallback
: undefined;
waitForCompletion = false;
errorHandler?: CronJobParams<OC, C>['errorHandler'];

private _isCallbackRunning = false;
private _timeout?: NodeJS.Timeout;
Expand All @@ -41,7 +42,8 @@ export class CronJob<OC extends CronOnCompleteCommand | null = null, C = null> {
runOnInit?: CronJobParams<OC, C>['runOnInit'],
utcOffset?: null,
unrefTimeout?: CronJobParams<OC, C>['unrefTimeout'],
waitForCompletion?: CronJobParams<OC, C>['waitForCompletion']
waitForCompletion?: CronJobParams<OC, C>['waitForCompletion'],
errorHandler?: CronJobParams<OC, C>['errorHandler']
);
constructor(
cronTime: CronJobParams<OC, C>['cronTime'],
Expand All @@ -53,7 +55,8 @@ export class CronJob<OC extends CronOnCompleteCommand | null = null, C = null> {
runOnInit?: CronJobParams<OC, C>['runOnInit'],
utcOffset?: CronJobParams<OC, C>['utcOffset'],
unrefTimeout?: CronJobParams<OC, C>['unrefTimeout'],
waitForCompletion?: CronJobParams<OC, C>['waitForCompletion']
waitForCompletion?: CronJobParams<OC, C>['waitForCompletion'],
errorHandler?: CronJobParams<OC, C>['errorHandler']
);
constructor(
cronTime: CronJobParams<OC, C>['cronTime'],
Expand All @@ -65,11 +68,14 @@ export class CronJob<OC extends CronOnCompleteCommand | null = null, C = null> {
runOnInit?: CronJobParams<OC, C>['runOnInit'],
utcOffset?: CronJobParams<OC, C>['utcOffset'],
unrefTimeout?: CronJobParams<OC, C>['unrefTimeout'],
waitForCompletion?: CronJobParams<OC, C>['waitForCompletion']
waitForCompletion?: CronJobParams<OC, C>['waitForCompletion'],
errorHandler?: CronJobParams<OC, C>['errorHandler']
) {
this.context = (context ?? this) as CronContext<C>;
this.waitForCompletion = Boolean(waitForCompletion);

this.errorHandler = errorHandler;

// runtime check for JS users
if (timeZone != null && utcOffset != null) {
throw new ExclusiveParametersError('timeZone', 'utcOffset');
Expand Down Expand Up @@ -128,7 +134,8 @@ export class CronJob<OC extends CronOnCompleteCommand | null = null, C = null> {
params.runOnInit,
params.utcOffset,
params.unrefTimeout,
params.waitForCompletion
params.waitForCompletion,
params.errorHandler
);
} else if (params.utcOffset != null) {
return new CronJob<OC, C>(
Expand All @@ -141,7 +148,8 @@ export class CronJob<OC extends CronOnCompleteCommand | null = null, C = null> {
params.runOnInit,
params.utcOffset,
params.unrefTimeout,
params.waitForCompletion
params.waitForCompletion,
params.errorHandler
);
} else {
return new CronJob<OC, C>(
Expand All @@ -154,7 +162,8 @@ export class CronJob<OC extends CronOnCompleteCommand | null = null, C = null> {
params.runOnInit,
params.utcOffset,
params.unrefTimeout,
params.waitForCompletion
params.waitForCompletion,
params.errorHandler
);
}
}
Expand Down Expand Up @@ -224,7 +233,8 @@ export class CronJob<OC extends CronOnCompleteCommand | null = null, C = null> {
if (this.waitForCompletion) await result;
}
} catch (error) {
console.error('[Cron] error in callback', error);
if (this.errorHandler != null) this.errorHandler(error);
else console.error('[Cron] error in callback', error);
} finally {
this._isCallbackRunning = false;
}
Expand Down
1 change: 1 addition & 0 deletions src/types/cron.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface BaseCronJobParams<
runOnInit?: boolean | null;
unrefTimeout?: boolean | null;
waitForCompletion?: boolean | null;
errorHandler?: ((error: unknown) => void) | null;
}

export type CronJobParams<
Expand Down
38 changes: 38 additions & 0 deletions tests/cron.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,44 @@ describe('cron', () => {
expect(callback).toHaveBeenCalledTimes(1);
});

it('should catch errors every time, if errorHandler is provided', () => {
const clock = sinon.useFakeTimers();
const errorFunc = jest.fn().mockImplementation(() => {
throw Error('Exception');
});
const handlerFunc = jest.fn();
const job = CronJob.from({
cronTime: '* * * * * *',
onTick: errorFunc,
errorHandler: handlerFunc,
start: true
});
clock.tick(1000);
expect(errorFunc).toHaveBeenCalledTimes(1);
expect(handlerFunc).toHaveBeenCalledTimes(1);
expect(handlerFunc).toHaveBeenLastCalledWith(new Error('Exception'));
clock.tick(1000);
expect(errorFunc).toHaveBeenCalledTimes(2);
expect(handlerFunc).toHaveBeenCalledTimes(2);
expect(handlerFunc).toHaveBeenLastCalledWith(new Error('Exception'));

job.stop();
clock.restore();
});

it('should throw errors if errorHandler is NOT provided', () => {
const errorFunc = jest.fn().mockImplementation(() => {
throw Error('Exception');
});
expect(() => {
CronJob.from({
cronTime: '* * * * * *',
onTick: errorFunc,
runOnInit: true
});
}).toThrow('Exception');
});

describe('waitForCompletion and job status tracking', () => {
it('should wait for async job completion when waitForCompletion is true', async () => {
const clock = sinon.useFakeTimers();
Expand Down

0 comments on commit bcf9282

Please sign in to comment.