Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 log errors if errorHandler is NOT provided', () => {
const errorFunc = jest.fn().mockImplementation(() => {
throw Error('Exception');
});
console.error = jest.fn();
CronJob.from({
cronTime: '* * * * * *',
onTick: errorFunc,
runOnInit: true
});
expect(console.error).toHaveBeenCalled();
});

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