Skip to content

Commit

Permalink
fix: Let ParallelHandler only run able handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
joachimvh committed Sep 9, 2024
1 parent 063e3c9 commit 3f3d636
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 16 deletions.
15 changes: 15 additions & 0 deletions src/HandlerUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,18 @@ Promise<AsyncHandler<TIn, TOut>[]> {

throw new AggregateError(errors, 'No handler can handle the input');
}

/**
* Returns the value of the `handle` call if the handle can handle input.
*
* @param handler - Handler to run the input.
* @param input - Input to handle.
*/
export async function handleIfAble<TIn, TOut>(handler: AsyncHandler<TIn, TOut>, input: TIn): Promise<TOut | void> {
try {
await handler.canHandle(input);
} catch {
return;
}
return handler.handle(input);
}
4 changes: 3 additions & 1 deletion src/ParallelHandler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { AsyncHandler } from './AsyncHandler';
import { handleIfAble } from './HandlerUtil';

/**
* A composite handler that executes handlers in parallel.
* Handlers that can not handler the input will be ignored.
* The `canHandle` check of this handler will always succeed.
*/
export class ParallelHandler<TIn = void> extends AsyncHandler<TIn> {
Expand All @@ -14,7 +16,7 @@ export class ParallelHandler<TIn = void> extends AsyncHandler<TIn> {

public async handle(input: TIn): Promise<void> {
await Promise.all(
this.handlers.map(async(handler): Promise<unknown> => handler.handleSafe(input)),
this.handlers.map(async(handler): Promise<unknown> => handleIfAble(handler, input)),
);
}
}
6 changes: 2 additions & 4 deletions src/SequenceHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ export class SequenceHandler<TIn = void, TOut = void> extends AsyncHandler<TIn,
public async handle(input: TIn): Promise<TOut | undefined> {
let result: TOut | undefined;
for (const handler of this.handlers) {
let supported: boolean;
let supported = false;
try {
await handler.canHandle(input);
supported = true;
} catch {
supported = false;
}
} catch {}
if (supported) {
result = await handler.handle(input);
}
Expand Down
13 changes: 12 additions & 1 deletion test/unit/HandlerUtil.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AsyncHandler } from '../../src/AsyncHandler';
import { filterHandlers, findHandler } from '../../src/HandlerUtil';
import { filterHandlers, findHandler, handleIfAble } from '../../src/HandlerUtil';
import { getError } from '../TestUtil';

describe('HandlerUtil', (): void => {
Expand All @@ -9,6 +9,7 @@ describe('HandlerUtil', (): void => {
beforeEach(async(): Promise<void> => {
handlerTrue = {
canHandle: jest.fn(),
handle: jest.fn().mockResolvedValue(true),
} satisfies Partial<AsyncHandler<any, any>> as any;
handlerFalse = {
canHandle: jest.fn().mockRejectedValue(new Error('test')),
Expand Down Expand Up @@ -45,4 +46,14 @@ describe('HandlerUtil', (): void => {
expect(error.errors[1].message).toBe('test');
});
});

describe('#handleIfAble', (): void => {
it('returns undefined if the handler cannot handle the input.', async(): Promise<void> => {
await expect(handleIfAble(handlerFalse, 'a')).resolves.toBeUndefined();
});

it('returns the value if the handler can handle the input.', async(): Promise<void> => {
await expect(handleIfAble(handlerTrue, 'a')).resolves.toBe(true);
});
});
});
25 changes: 15 additions & 10 deletions test/unit/ParallelHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,38 @@ describe('ParallelHandler', (): void => {
beforeEach(async(): Promise<void> => {
handlers = [
{
handleSafe: jest.fn().mockResolvedValue('0'),
canHandle: jest.fn(),
handle: jest.fn().mockResolvedValue('0'),
} satisfies Partial<AsyncHandler<string, string>> as any,
{
handleSafe: jest.fn().mockResolvedValue('1'),
canHandle: jest.fn(),
handle: jest.fn().mockResolvedValue('1'),
} satisfies Partial<AsyncHandler<string, string>> as any,
{
handleSafe: jest.fn().mockResolvedValue('2'),
canHandle: jest.fn(),
handle: jest.fn().mockResolvedValue('2'),
} satisfies Partial<AsyncHandler<string, string>> as any,
];

parallel = new ParallelHandler(handlers);
});

it('can handle all requests.', async(): Promise<void> => {
handlers[0].handleSafe.mockRejectedValueOnce(new Error('error'));
handlers[0].canHandle.mockRejectedValueOnce(new Error('error'));
handlers[1].handle.mockRejectedValueOnce(new Error('error'));
await expect(parallel.canHandle('input')).resolves.toBeUndefined();
});

it('runs all handlers that can handle the input.', async(): Promise<void> => {
handlers[0].canHandle.mockRejectedValueOnce(new Error('error'));
await expect(parallel.handle('abc')).resolves.toBeUndefined();

expect(handlers[0].handleSafe).toHaveBeenCalledTimes(1);
expect(handlers[1].handleSafe).toHaveBeenCalledTimes(1);
expect(handlers[2].handleSafe).toHaveBeenCalledTimes(1);
expect(handlers[0].canHandle).toHaveBeenLastCalledWith('abc');
expect(handlers[1].canHandle).toHaveBeenLastCalledWith('abc');
expect(handlers[2].canHandle).toHaveBeenLastCalledWith('abc');

expect(handlers[0].handleSafe).toHaveBeenCalledWith('abc');
expect(handlers[1].handleSafe).toHaveBeenCalledWith('abc');
expect(handlers[2].handleSafe).toHaveBeenCalledWith('abc');
expect(handlers[0].handle).toHaveBeenCalledTimes(0);
expect(handlers[1].handle).toHaveBeenLastCalledWith('abc');
expect(handlers[2].handle).toHaveBeenLastCalledWith('abc');
});
});

0 comments on commit 3f3d636

Please sign in to comment.