Skip to content

Commit ab950e0

Browse files
authored
feat: throws (#51)
* use .call for deoptimization * throws option and create src/types * update * update
1 parent f8d0ada commit ab950e0

File tree

10 files changed

+275
-246
lines changed

10 files changed

+275
-246
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ export type Options = {
110110
*/
111111
signal?: AbortSignal;
112112

113+
/**
114+
* Throw if a task fails (events will not work if true)
115+
*/
116+
throws?: boolean;
117+
113118
/**
114119
* warmup time (milliseconds) @default 100ms
115120
*/

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"publish": "npm run build && clean-publish",
1111
"typecheck": "tsc --noEmit",
1212
"release": "bumpp package.json --commit --push --tag && npm run publish",
13-
"test": "vitest --run"
13+
"test": "vitest --retry=3 --run"
1414
},
1515
"main": "./dist/index.cjs",
1616
"module": "./dist/index.js",

src/bench.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {
66
TaskResult,
77
BenchEventsMap,
88
FnOptions,
9-
} from '../types/index';
9+
} from './types';
1010
import { createBenchEvent } from './event';
1111
import Task from './task';
1212
import { AddEventListenerOptionsArgument, RemoveEventListenerOptionsArgument } from './types';
@@ -26,6 +26,8 @@ export default class Bench extends EventTarget {
2626

2727
signal?: AbortSignal;
2828

29+
throws: boolean;
30+
2931
warmupTime = 100;
3032

3133
warmupIterations = 5;
@@ -48,6 +50,7 @@ export default class Bench extends EventTarget {
4850
this.time = options.time ?? this.time;
4951
this.iterations = options.iterations ?? this.iterations;
5052
this.signal = options.signal;
53+
this.throws = options.throws ?? false;
5154
// eslint-disable-next-line @typescript-eslint/no-empty-function
5255
this.setup = options.setup ?? (() => {});
5356
// eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -158,10 +161,10 @@ export default class Bench extends EventTarget {
158161
if (result) {
159162
return {
160163
'Task Name': name,
161-
'ops/sec': parseInt(result.hz.toString(), 10).toLocaleString(),
162-
'Average Time (ns)': result.mean * 1000 * 1000,
163-
Margin: `\xb1${result.rme.toFixed(2)}%`,
164-
Samples: result.samples.length,
164+
'ops/sec': result.error ? 'NaN' : parseInt(result.hz.toString(), 10).toLocaleString(),
165+
'Average Time (ns)': result.error ? 'NaN' : result.mean * 1000 * 1000,
166+
Margin: result.error ? 'NaN' : `\xb1${result.rme.toFixed(2)}%`,
167+
Samples: result.error ? 'NaN' : result.samples.length,
165168
};
166169
}
167170
return null;

src/event.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { BenchEvents } from '../types';
1+
import type { BenchEvents } from './types';
22
import Task from './task';
33

44
function createBenchEvent(

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export type {
99
Hook,
1010
TaskEvents,
1111
BenchEvent,
12-
} from '../types';
12+
} from './types';
1313

1414
export { now, hrtimeNow } from './utils';
1515

src/task.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {
44
TaskResult,
55
TaskEventsMap,
66
FnOptions,
7-
} from '../types/index';
7+
} from './types';
88
import Bench from './bench';
99
import tTable from './constants';
1010
import { createBenchEvent } from './event';
@@ -100,6 +100,9 @@ export default class Task extends EventTarget {
100100
}
101101
} catch (e) {
102102
this.setResult({ error: e });
103+
if (this.bench.throws) {
104+
throw e;
105+
}
103106
}
104107

105108
if (this.opts.afterAll != null) {
@@ -217,8 +220,10 @@ export default class Task extends EventTarget {
217220
} else {
218221
this.fn.call(this);
219222
}
220-
} catch {
221-
// todo
223+
} catch (e) {
224+
if (this.bench.throws) {
225+
throw e;
226+
}
222227
}
223228

224229
this.runs += 1;

src/types.ts

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,243 @@
1+
import Task from '../src/task';
2+
3+
/**
4+
* the task function
5+
*/
6+
export type Fn = () => any | Promise<any>;
7+
8+
export interface FnOptions {
9+
/**
10+
* An optional function that is run before iterations of this task begin
11+
*/
12+
beforeAll?: (this: Task) => void | Promise<void>;
13+
14+
/**
15+
* An optional function that is run before each iteration of this task
16+
*/
17+
beforeEach?: (this: Task) => void | Promise<void>;
18+
19+
/**
20+
* An optional function that is run after each iteration of this task
21+
*/
22+
afterEach?: (this: Task) => void | Promise<void>;
23+
24+
/**
25+
* An optional function that is run after all iterations of this task end
26+
*/
27+
afterAll?: (this: Task) => void | Promise<void>;
28+
}
29+
30+
/**
31+
* the benchmark task result object
32+
*/
33+
export type TaskResult = {
34+
/*
35+
* the last error that was thrown while running the task
36+
*/
37+
error?: unknown;
38+
39+
/**
40+
* The amount of time in milliseconds to run the benchmark task (cycle).
41+
*/
42+
totalTime: number;
43+
44+
/**
45+
* the minimum value in the samples
46+
*/
47+
min: number;
48+
/**
49+
* the maximum value in the samples
50+
*/
51+
max: number;
52+
53+
/**
54+
* the number of operations per second
55+
*/
56+
hz: number;
57+
58+
/**
59+
* how long each operation takes (ms)
60+
*/
61+
period: number;
62+
63+
/**
64+
* task samples of each task iteration time (ms)
65+
*/
66+
samples: number[];
67+
68+
/**
69+
* samples mean/average (estimate of the population mean)
70+
*/
71+
mean: number;
72+
73+
/**
74+
* samples variance (estimate of the population variance)
75+
*/
76+
variance: number;
77+
78+
/**
79+
* samples standard deviation (estimate of the population standard deviation)
80+
*/
81+
sd: number;
82+
83+
/**
84+
* standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean)
85+
*/
86+
sem: number;
87+
88+
/**
89+
* degrees of freedom
90+
*/
91+
df: number;
92+
93+
/**
94+
* critical value of the samples
95+
*/
96+
critical: number;
97+
98+
/**
99+
* margin of error
100+
*/
101+
moe: number;
102+
103+
/**
104+
* relative margin of error
105+
*/
106+
rme: number;
107+
108+
/**
109+
* p75 percentile
110+
*/
111+
p75: number;
112+
113+
/**
114+
* p99 percentile
115+
*/
116+
p99: number;
117+
118+
/**
119+
* p995 percentile
120+
*/
121+
p995: number;
122+
123+
/**
124+
* p999 percentile
125+
*/
126+
p999: number;
127+
};
128+
129+
/**
130+
* Both the `Task` and `Bench` objects extend the `EventTarget` object,
131+
* so you can attach a listeners to different types of events
132+
* to each class instance using the universal `addEventListener` and
133+
* `removeEventListener`
134+
*/
135+
136+
/**
137+
* Bench events
138+
*/
139+
export type BenchEvents =
140+
| 'abort' // when a signal aborts
141+
| 'complete' // when running a benchmark finishes
142+
| 'error' // when the benchmark task throws
143+
| 'reset' // when the reset function gets called
144+
| 'start' // when running the benchmarks gets started
145+
| 'warmup' // when the benchmarks start getting warmed up (before start)
146+
| 'cycle' // when running each benchmark task gets done (cycle)
147+
| 'add' // when a Task gets added to the Bench
148+
| 'remove' // when a Task gets removed of the Bench
149+
| 'todo'; // when a todo Task gets added to the Bench
150+
151+
export type Hook = (task: Task, mode: 'warmup' | 'run') => void | Promise<void>;
152+
153+
type NoopEventListener = () => any | Promise<any>
154+
type TaskEventListener = (e: Event & { task: Task }) => any | Promise<any>
155+
156+
export interface BenchEventsMap{
157+
abort: NoopEventListener
158+
start: NoopEventListener
159+
complete: NoopEventListener
160+
warmup: NoopEventListener
161+
reset: NoopEventListener
162+
add: TaskEventListener
163+
remove: TaskEventListener
164+
cycle: TaskEventListener
165+
error: TaskEventListener
166+
todo: TaskEventListener
167+
}
168+
169+
/**
170+
* task events
171+
*/
172+
export type TaskEvents =
173+
| 'abort'
174+
| 'complete'
175+
| 'error'
176+
| 'reset'
177+
| 'start'
178+
| 'warmup'
179+
| 'cycle';
180+
181+
export type TaskEventsMap = {
182+
abort: NoopEventListener
183+
start: TaskEventListener
184+
error: TaskEventListener
185+
cycle: TaskEventListener
186+
complete: TaskEventListener
187+
warmup: TaskEventListener
188+
reset: TaskEventListener
189+
}
190+
export type Options = {
191+
/**
192+
* time needed for running a benchmark task (milliseconds) @default 500
193+
*/
194+
time?: number;
195+
196+
/**
197+
* number of times that a task should run if even the time option is finished @default 10
198+
*/
199+
iterations?: number;
200+
201+
/**
202+
* function to get the current timestamp in milliseconds
203+
*/
204+
now?: () => number;
205+
206+
/**
207+
* An AbortSignal for aborting the benchmark
208+
*/
209+
signal?: AbortSignal;
210+
211+
/**
212+
* Throw if a task fails (events will not work if true)
213+
*/
214+
throws?: boolean;
215+
216+
/**
217+
* warmup time (milliseconds) @default 100ms
218+
*/
219+
warmupTime?: number;
220+
221+
/**
222+
* warmup iterations @default 5
223+
*/
224+
warmupIterations?: number;
225+
226+
/**
227+
* setup function to run before each benchmark task (cycle)
228+
*/
229+
setup?: Hook;
230+
231+
/**
232+
* teardown function to run after each benchmark task (cycle)
233+
*/
234+
teardown?: Hook;
235+
};
236+
237+
export type BenchEvent = Event & {
238+
task: Task | null;
239+
};
240+
1241
// @types/node doesn't have these types globally, and we don't want to bring "dom" lib for everyone
2242

3243
export type RemoveEventListenerOptionsArgument = Parameters<typeof EventTarget.prototype.removeEventListener>[2];

src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Fn } from '../types/index';
1+
import type { Fn } from './types';
22
import type Task from './task';
33

44
export const nanoToMs = (nano: number) => nano / 1e6;

test/index.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,16 @@ test('error event', async () => {
221221
expect(taskErr!).toBe(err);
222222
});
223223

224+
test('throws', async () => {
225+
const bench = new Bench({ iterations: 1, throws: true });
226+
const err = new Error();
227+
228+
bench.add('error', () => {
229+
throw err;
230+
});
231+
expect(() => bench.run()).rejects.toThrowError(err);
232+
});
233+
224234
test('detect faster task', async () => {
225235
const bench = new Bench({ time: 200 });
226236
bench

0 commit comments

Comments
 (0)