Skip to content

Commit

Permalink
feat(breaking): return errors instead of throwing
Browse files Browse the repository at this point in the history
  • Loading branch information
KATT committed Nov 29, 2024
1 parent 69193ca commit 8484c7a
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 18 deletions.
9 changes: 7 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@ emitter.emit('open', 1);
emitter.emit('other');
```
*/

export type EmitErrorResult = [unknown, ...unknown[]];
export type EmitSuccessResult = undefined;
export type EmitResult = EmitErrorResult | EmitSuccessResult;

export default class Emittery<
EventData = Record<EventName, any>, // TODO: Use `unknown` instead of `any`.
AllEventData = EventData & OmnipresentEventData,
Expand Down Expand Up @@ -492,11 +497,11 @@ export default class Emittery<
@returns A promise that resolves when all the event listeners are done. *Done* meaning executed if synchronous or resolved when an async/promise-returning function. You usually wouldn't want to wait for this, but you could for example catch possible errors. If any of the listeners throw/reject, the returned promise will be rejected with the error, but the other listeners will not be affected.
*/
emit<Name extends DatalessEvents>(eventName: Name): Promise<void>;
emit<Name extends DatalessEvents>(eventName: Name): Promise<EmitResult>;
emit<Name extends keyof EventData>(
eventName: Name,
eventData: EventData[Name]
): Promise<void>;
): Promise<EmitResult>;

/**
Same as `emit()`, but it waits for each listener to resolve before triggering the next one. This can be useful if your events depend on each other. Although ideally they should not. Prefer `emit()` whenever possible.
Expand Down
21 changes: 19 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,18 +361,35 @@ export default class Emittery {
const staticAnyListeners = isMetaEvent(eventName) ? [] : [...anyListeners];

await resolvedPromise;

const errors = [];

async function runListener(listener, ...args) {
try {
await listener(...args);
} catch (error) {
errors.push(error);
}
}

await Promise.all([
...staticListeners.map(async listener => {
if (listeners.has(listener)) {
return listener(eventData);
await runListener(listener, eventData);
}
}),
...staticAnyListeners.map(async listener => {
if (anyListeners.has(listener)) {
return listener(eventName, eventData);
await runListener(listener, eventName, eventData);
}
}),
]);

if (errors.length > 0) {
return errors;
}

return undefined;
}

async emitSerial(eventName, eventData) {
Expand Down
43 changes: 29 additions & 14 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -588,20 +588,6 @@ test('emit() - returns undefined', async t => {
t.is(await emitter.emit('πŸ¦„πŸ¦„'), undefined);
});

test('emit() - throws an error if any listener throws', async t => {
const emitter = new Emittery();

emitter.on('πŸ¦„', () => {
throw new Error('🌈');
});
await t.throwsAsync(emitter.emit('πŸ¦„'), {instanceOf: Error});

emitter.on('πŸ¦„πŸ¦„', async () => {
throw new Error('🌈');
});
await t.throwsAsync(emitter.emit('πŸ¦„πŸ¦„'), {instanceOf: Error});
});

test('emitSerial()', async t => {
const emitter = new Emittery();
const promise = pEvent(emitter, 'πŸ¦„');
Expand Down Expand Up @@ -1333,3 +1319,32 @@ test('debug mode - handles circular references in event data', async t => {

await t.notThrowsAsync(emitter.emit('test', data));
});

test('emit() - returns null when all listeners succeed', async t => {
const emitter = new Emittery();

emitter.on('test', () => {});
emitter.on('test', async () => {});

const result = await emitter.emit('test', 'data');
t.is(result, undefined);
});

test('emit() - returns array of errors when listeners fail', async t => {
const emitter = new Emittery();
const syncError = new Error('sync error');
const asyncError = new Error('async error');

emitter.on('test', () => {
throw syncError;
});
emitter.on('test', async () => {
throw asyncError;
});

const result = await emitter.emit('test', 'data');
t.true(Array.isArray(result));
t.is(result.length, 2);
t.is(result[0], syncError);
t.is(result[1], asyncError);
});

0 comments on commit 8484c7a

Please sign in to comment.