From be2e92532f4a4b8f0b2c9e12d4adf942d380423e Mon Sep 17 00:00:00 2001 From: Odin Thomas Rochmann Date: Tue, 27 Aug 2024 12:41:25 +0200 Subject: [PATCH] fix(utils/query): Add try-catch block to handle exceptions in query function execution The try-catch block is added to the execution of the query function in order to handle exceptions that might occur before returning the observable. This ensures that any errors thrown during the execution are caught and properly handled, preventing the process from terminating prematurely. Updated test-case to validate exceptions in query client --- .changeset/curly-roses-train.md | 25 +++++++++++++++ packages/utils/query/src/client/flows.ts | 32 +++++++++++-------- .../utils/query/tests/Query.query.test.ts | 21 ++++++------ 3 files changed, 54 insertions(+), 24 deletions(-) create mode 100644 .changeset/curly-roses-train.md diff --git a/.changeset/curly-roses-train.md b/.changeset/curly-roses-train.md new file mode 100644 index 000000000..16405d8a7 --- /dev/null +++ b/.changeset/curly-roses-train.md @@ -0,0 +1,25 @@ +--- +'@equinor/fusion-query': patch +--- + +Added try catch block to execution of query function, since the method might throw an exception before returning the observable. + +```ts +const queryClient = new Query({ + client: { + () => { + throw new Error('this would terminate the process before'); + return new Observable((subscriber) => { + throw new Error('this worked before'); + }); + }, + }, + key: (value) => value, + +queryClient.query().subscribe({ + error: (error) => { + // before changes this would not be called since stream would be terminated. + console.log(error.message); // this would print 'this would terminate the process before' + }, +}); +``` diff --git a/packages/utils/query/src/client/flows.ts b/packages/utils/query/src/client/flows.ts index 8738cc6cf..6f3d3b614 100644 --- a/packages/utils/query/src/client/flows.ts +++ b/packages/utils/query/src/client/flows.ts @@ -59,19 +59,25 @@ export const handleExecution = }), ); - // Execute the fetch operation, passing the AbortSignal to allow for cancellation. - return from(fetch(request.args, controller.signal)).pipe( - map((value) => - actions.execute.success({ - ...request, - status: 'complete', - completed: Date.now(), - value, - }), - ), - catchError((err) => of(actions.execute.failure(err, transaction))), - takeUntil(cancel$), // Complete the observable chain if a cancel action is received. - ); + try { + return from(fetch(request.args, controller.signal)).pipe( + map((value) => + actions.execute.success({ + ...request, + status: 'complete', + completed: Date.now(), + value, + }), + ), + catchError((err) => of(actions.execute.failure(err, transaction))), + takeUntil(cancel$), // Complete the observable chain if a cancel action is received. + ); + } catch (err) { + // Normally errors thrown during the execution of the fetch function are caught by the `catchError` operator. + // However, if the fetch function itself throws an error, it will be caught here. + // This can happen if the fetch function is not a function or if it throws synchronously. + return of(actions.execute.failure(err as Error, transaction)); + } }), ); diff --git a/packages/utils/query/tests/Query.query.test.ts b/packages/utils/query/tests/Query.query.test.ts index 70c827f01..04b4d5b2c 100644 --- a/packages/utils/query/tests/Query.query.test.ts +++ b/packages/utils/query/tests/Query.query.test.ts @@ -58,8 +58,11 @@ describe('Query query', () => { it('should handle errors', async () => { // Mock a function that always throws an error when called - const fn = vi.fn(async (_: string) => { - throw Error('error'); + const fn = vi.fn((arg: string) => { + if (fn.mock.calls.length === 1) { + throw Error('error'); + } + return Promise.resolve(arg); }); // Initialize the Query client with the mocked function that throws an error const queryClient = new Query({ @@ -68,15 +71,11 @@ describe('Query query', () => { }, key: (value) => value, // Use the input value as the query key }); - try { - // Attempt to perform a query that is expected to fail - await lastValueFrom(queryClient.query('foo')); - } catch (e) { - // Verify that the caught exception is an instance of Error - expect(e).toBeInstanceOf(Error); - // Verify that the error message is what was thrown by the mocked function - expect(e.message).toBe('error'); - } + + await expect(lastValueFrom(queryClient.query('foo'))).rejects.toThrowError('error'); + await expect(lastValueFrom(queryClient.query('foo'))).resolves.toMatchObject({ + value: 'foo', + }); }); it('should cancel requests when no longer subscribed', async () => {