Skip to content

Commit

Permalink
Error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
bmingles committed Jan 19, 2024
1 parent 7d20ebb commit c2b029c
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 4 deletions.
56 changes: 53 additions & 3 deletions packages/react-hooks/src/useAsyncInterval.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import { renderHook, act } from '@testing-library/react-hooks';
import Log from '@deephaven/log';
import { TestUtils } from '@deephaven/utils';
import useAsyncInterval from './useAsyncInterval';

jest.mock('@deephaven/log', () => {
const logger = {
error: jest.fn(),
};
return {
__esModule: true,
default: {
module: jest.fn(() => logger),
},
};
});

const { asMock } = TestUtils;

const mockLoggerInstance = Log.module('mock.logger');

beforeEach(() => {
jest.clearAllMocks();
expect.hasAssertions();
Expand All @@ -16,12 +31,22 @@ afterAll(() => {
});

describe('useAsyncInterval', () => {
function createCallback(ms: number) {
/**
* Creates a callback function that resolves after the given number of
* milliseconds. Accepts an optional array of booleans that determine whether
* calls to the callback will reject instead of resolve. They are mapped by
* index to the order in which the callback is called.
*/
function createCallback(ms: number, rejectWith: (Error | undefined)[] = []) {
return jest
.fn(
async (): Promise<void> =>
new Promise(resolve => {
setTimeout(resolve, ms);
new Promise((resolve, reject) => {
const rejectArg = rejectWith.shift();
setTimeout(
rejectArg == null ? resolve : () => reject(rejectArg),
ms
);

// Don't track the above call to `setTimeout`
asMock(setTimeout).mock.calls.pop();
Expand Down Expand Up @@ -155,4 +180,29 @@ describe('useAsyncInterval', () => {

expect(window.setTimeout).not.toHaveBeenCalled();
});

it('should handle tick errors', async () => {
const callbackDelayMs = 50;
const mockError = new Error('mock.error');
const callback = createCallback(callbackDelayMs, [mockError, undefined]);

renderHook(() => useAsyncInterval(callback, targetIntervalMs));

// First callback fires immediately
expect(callback).toHaveBeenCalledTimes(1);

// Mimick the callback Promise rejecting
act(() => jest.advanceTimersByTime(callbackDelayMs));
await TestUtils.flushPromises();

expect(mockLoggerInstance.error).toHaveBeenCalledWith(
'A tick error occurred:',
mockError
);

// Advance to next interval
act(() => jest.advanceTimersByTime(targetIntervalMs));

expect(callback).toHaveBeenCalledTimes(2);
});
});
9 changes: 8 additions & 1 deletion packages/react-hooks/src/useAsyncInterval.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useCallback, useEffect, useRef } from 'react';
import Log from '@deephaven/log';
import { useIsMountedRef } from './useIsMountedRef';

const log = Log.module('useAsyncInterval');

/**
* Calls the given async callback at a target interval.
*
Expand Down Expand Up @@ -40,7 +43,11 @@ export function useAsyncInterval(

trackingStartedRef.current = now;

await callback();
try {
await callback();
} catch (err) {
log.error('A tick error occurred:', err);
}

if (!isMountedRef.current) {
return;
Expand Down

0 comments on commit c2b029c

Please sign in to comment.