Repository: tinylibs/tinybench
Pull request #167 (2024-10-27 13:20:35)

fix: ensure error events are sent

pkg-pr-new[bot]: 
[Open in Stackblitz](https://pkg.pr.new/template/8e988ee7-7e01-448c-8206-c4faa5caff49)


```
pnpm add https://pkg.pr.new/tinylibs/tinybench@167
```


_commit: <a href="https://github.com/tinylibs/tinybench/runs/32122163290"><code>bb8da15</code></a>_


Patch:
diff --git a/README.md b/README.md
index 0774888..2ea056e 100644
--- a/README.md
+++ b/README.md
@@ -103,7 +103,7 @@ export interface Options {
   signal?: AbortSignal;
 
   /**
-   * Throw if a task fails (events will not work if true) @default false
+   * Throws if a task fails @default false
    */
   throws?: boolean;
 
@@ -369,7 +369,7 @@ task.addEventListener('cycle', (evt) => {
 ### `BenchEvent`
 
 ```ts
-export type BenchEvent = Event & { task?: Task };
+export type BenchEvent = Event & { error?: Error; task?: Task };
 ```
 
 ### `process.hrtime`
diff --git a/src/bench.ts b/src/bench.ts
index 8962a53..7d4de86 100644
--- a/src/bench.ts
+++ b/src/bench.ts
@@ -101,7 +101,7 @@ export default class Bench extends EventTarget {
       const limit = pLimit(this.threshold);
       const promises: Promise<void>[] = [];
       for (const task of this._tasks.values()) {
-        promises.push(limit(() => task.warmup()));
+        promises.push(limit(task.warmup.bind(task)));
       }
       await Promise.all(promises);
     } else {
@@ -124,7 +124,7 @@ export default class Bench extends EventTarget {
       const limit = pLimit(this.threshold);
       const promises: Promise<Task>[] = [];
       for (const task of this._tasks.values()) {
-        promises.push(limit(() => task.run()));
+        promises.push(limit(task.run.bind(task)));
       }
       values = await Promise.all(promises);
     } else {
diff --git a/src/event.ts b/src/event.ts
index 3f496e5..e18c9a1 100644
--- a/src/event.ts
+++ b/src/event.ts
@@ -14,5 +14,21 @@ function createBenchEvent(eventType: BenchEvents, target?: Task) {
   return event;
 }
 
-// eslint-disable-next-line import/prefer-default-export
-export { createBenchEvent };
+function createErrorEvent(target: Task, error: Error) {
+  const event = new Event('error');
+  Object.defineProperty(event, 'task', {
+    value: target,
+    enumerable: true,
+    writable: false,
+    configurable: false,
+  });
+  Object.defineProperty(event, 'error', {
+    value: error,
+    enumerable: true,
+    writable: false,
+    configurable: false,
+  });
+  return event;
+}
+
+export { createBenchEvent, createErrorEvent };
diff --git a/src/task.ts b/src/task.ts
index 49617b6..a7453ef 100644
--- a/src/task.ts
+++ b/src/task.ts
@@ -1,6 +1,6 @@
 import pLimit from 'p-limit';
 import type Bench from './bench';
-import { createBenchEvent } from './event';
+import { createBenchEvent, createErrorEvent } from './event';
 import type {
   AddEventListenerOptionsArgument,
   Fn,
@@ -148,11 +148,11 @@ export default class Task extends EventTarget {
 
     if (error) {
       this.setResult({ error });
+      this.dispatchEvent(createErrorEvent(this, error));
+      this.bench.dispatchEvent(createErrorEvent(this, error));
       if (this.bench.throws) {
         throw error;
       }
-      this.dispatchEvent(createBenchEvent('error', this));
-      this.bench.dispatchEvent(createBenchEvent('error', this));
     }
   }
 
@@ -217,11 +217,11 @@ export default class Task extends EventTarget {
 
     if (error) {
       this.setResult({ error });
+      this.dispatchEvent(createErrorEvent(this, error));
+      this.bench.dispatchEvent(createErrorEvent(this, error));
       if (this.bench.throws) {
         throw error;
       }
-      this.dispatchEvent(createBenchEvent('error', this));
-      this.bench.dispatchEvent(createBenchEvent('error', this));
     }
 
     this.dispatchEvent(createBenchEvent('cycle', this));
diff --git a/src/types.ts b/src/types.ts
index 86fb2a9..24a550c 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -269,7 +269,7 @@ export type BenchEvents =
 
 export type Hook = (task: Task, mode: 'warmup' | 'run') => void | Promise<void>;
 
-export type BenchEvent = Event & { task?: Task };
+export type BenchEvent = Event & { error?: Error; task?: Task };
 
 export type EventListener = (evt: BenchEvent) => void;
 
@@ -334,7 +334,7 @@ export interface Options {
   signal?: AbortSignal;
 
   /**
-   * Throw if a task fails (events will not work if true) @default false
+   * Throws if a task fails @default false
    */
   throws?: boolean;
 


Test patch:
diff --git a/test/index.test.ts b/test/index.test.ts
index f784222..b46c22e 100644
--- a/test/index.test.ts
+++ b/test/index.test.ts
@@ -198,21 +198,23 @@ test('events order at task completion', async () => {
 
 test.each(['warmup', 'run'])('%s error event', async (mode) => {
   const bench = new Bench({ time: 100, warmup: mode === 'warmup' });
-  const err = new Error();
+  const error = new Error();
 
   bench.add('error', () => {
-    throw err;
+    throw error;
   });
 
-  let taskErr: Error | undefined;
+  let err: Error | undefined;
+  let task: Task | undefined;
   bench.addEventListener('error', (evt) => {
-    const { task } = evt;
-    taskErr = task?.result?.error;
+    const { error: e, task: t } = evt;
+    err = e;
+    task = t;
   });
 
-  await bench.run();
-
-  expect(taskErr).toStrictEqual(err);
+  await expect(bench.run()).resolves.toBeDefined();
+  expect(err).toStrictEqual(error);
+  expect(task?.result?.error).toStrictEqual(error);
 });
 
 test.each(['warmup', 'run'])('%s throws', async (mode) => {
@@ -223,12 +225,23 @@ test.each(['warmup', 'run'])('%s throws', async (mode) => {
     warmup: mode === 'warmup',
     warmupIterations: iterations,
   });
-  const err = new Error();
+  const error = new Error();
 
   bench.add('error', () => {
-    throw err;
+    throw error;
   });
-  await expect(() => bench.run()).rejects.toThrowError(err);
+
+  let err: Error | undefined;
+  let task: Task | undefined;
+  bench.addEventListener('error', (evt) => {
+    const { error: e, task: t } = evt;
+    err = e;
+    task = t;
+  });
+
+  await expect(bench.run()).rejects.toThrowError(error);
+  expect(err).toStrictEqual(error);
+  expect(task?.result?.error).toStrictEqual(error);
 });
 
 test('detect faster task', async () => {