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 () => {