Skip to content

Commit edb1d12

Browse files
docs: describe each hook class in the README and add small examples
1 parent a2b0d37 commit edb1d12

File tree

1 file changed

+190
-1
lines changed

1 file changed

+190
-1
lines changed

README.md

Lines changed: 190 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Other people can now use these hooks:
5151
```js
5252
const myCar = new Car();
5353

54-
// Use the tap method to add a consument
54+
// Use the tap method to add a consumer (plugin)
5555
myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on());
5656
```
5757

@@ -164,6 +164,195 @@ Additionally, hooks can be synchronous or asynchronous. To reflect this, there
164164

165165
The hook type is reflected in its class name. E.g., `AsyncSeriesWaterfallHook` allows asynchronous functions and runs them in series, passing each function’s return value into the next function.
166166

167+
## Hook classes
168+
169+
The table below summarizes the 9 built-in hook classes. For each class:
170+
171+
- **Tap methods** are the `tapX` variants that may be used to register a handler.
172+
- **Call methods** are the ways the owner of the hook can trigger it.
173+
- **Result** is the value returned from `call` (or passed to the `callAsync` callback / resolved from the `promise` call).
174+
- **Returned value from tap** describes whether the value returned from a tapped function has an effect.
175+
176+
| Class | Tap methods | Call methods | Result | Returned value from tap |
177+
| -------------------------- | ------------------------------- | ---------------------- | ----------------------------------------------- | ---------------------------------------------------- |
178+
| `SyncHook` | `tap` | `call` | `undefined` | ignored |
179+
| `SyncBailHook` | `tap` | `call` | first non-`undefined` value, or `undefined` | short-circuits the hook |
180+
| `SyncWaterfallHook` | `tap` | `call` | final value (first argument after the last tap) | passed as first argument to the next tap |
181+
| `SyncLoopHook` | `tap` | `call` | `undefined` | non-`undefined` restarts the loop from the first tap |
182+
| `AsyncParallelHook` | `tap`, `tapAsync`, `tapPromise` | `callAsync`, `promise` | `undefined` | ignored |
183+
| `AsyncParallelBailHook` | `tap`, `tapAsync`, `tapPromise` | `callAsync`, `promise` | first non-`undefined` value, or `undefined` | short-circuits the hook |
184+
| `AsyncSeriesHook` | `tap`, `tapAsync`, `tapPromise` | `callAsync`, `promise` | `undefined` | ignored |
185+
| `AsyncSeriesBailHook` | `tap`, `tapAsync`, `tapPromise` | `callAsync`, `promise` | first non-`undefined` value, or `undefined` | short-circuits the hook |
186+
| `AsyncSeriesLoopHook` | `tap`, `tapAsync`, `tapPromise` | `callAsync`, `promise` | `undefined` | non-`undefined` restarts the loop from the first tap |
187+
| `AsyncSeriesWaterfallHook` | `tap`, `tapAsync`, `tapPromise` | `callAsync`, `promise` | final value (first argument after the last tap) | passed as first argument to the next tap |
188+
189+
Detailed behavior of each class:
190+
191+
### SyncHook
192+
193+
A basic synchronous hook. Every tapped function is called in registration order with the arguments passed to `call`. Return values from tapped functions are ignored and `call` returns `undefined`.
194+
195+
- Tap methods: `tap`
196+
- Call methods: `call`
197+
- `tapAsync` and `tapPromise` throw an error.
198+
199+
```js
200+
const hook = new SyncHook(["name"]);
201+
hook.tap("A", (name) => console.log(`hello ${name}`));
202+
hook.tap("B", (name) => console.log(`hi ${name}`));
203+
hook.call("world");
204+
// hello world
205+
// hi world
206+
```
207+
208+
### SyncBailHook
209+
210+
A synchronous hook that allows exiting early. Every tapped function is called in order until one returns a non-`undefined` value; that value becomes the result of `call` and the remaining taps are skipped. If all taps return `undefined`, `call` returns `undefined`.
211+
212+
- Tap methods: `tap`
213+
- Call methods: `call`
214+
215+
```js
216+
const hook = new SyncBailHook(["value"]);
217+
hook.tap("Negative", (v) => (v < 0 ? "negative" : undefined));
218+
hook.tap("Zero", (v) => (v === 0 ? "zero" : undefined));
219+
hook.tap("Positive", (v) => "positive");
220+
221+
hook.call(-1); // "negative" (later taps skipped)
222+
hook.call(5); // "positive"
223+
```
224+
225+
### SyncWaterfallHook
226+
227+
A synchronous hook that threads a value through its tapped functions. The first argument passed to `call` is forwarded to the first tap. If a tap returns a non-`undefined` value it replaces that argument for the next tap; otherwise the previous value is kept. `call` returns the value after the last tap has run. Additional arguments (if any) are passed through unchanged.
228+
229+
- Tap methods: `tap`
230+
- Call methods: `call`
231+
232+
```js
233+
const hook = new SyncWaterfallHook(["value"]);
234+
hook.tap("Double", (v) => v * 2);
235+
hook.tap("PlusOne", (v) => v + 1);
236+
237+
hook.call(3); // 7 -> (3 * 2) + 1
238+
```
239+
240+
### SyncLoopHook
241+
242+
A synchronous hook that keeps re-running its taps until all of them return `undefined` for a full pass. Whenever a tap returns a non-`undefined` value the hook restarts from the first tap. `call` returns `undefined`.
243+
244+
- Tap methods: `tap`
245+
- Call methods: `call`
246+
247+
```js
248+
const hook = new SyncLoopHook(["state"]);
249+
let retries = 3;
250+
hook.tap("Retry", () => {
251+
if (retries-- > 0) return true; // non-undefined restarts the loop
252+
});
253+
hook.tap("Log", () => console.log("pass"));
254+
255+
hook.call({});
256+
// pass (runs once all taps return undefined)
257+
```
258+
259+
### AsyncParallelHook
260+
261+
An asynchronous hook that runs all of its tapped functions in parallel. It completes when every tap has signalled completion (sync return, callback, or promise resolution). Return values and resolution values are ignored; `callAsync`'s callback is invoked with no result and `promise()` resolves to `undefined`. If any tap errors, the error is forwarded and remaining taps still complete but their results are discarded.
262+
263+
- Tap methods: `tap`, `tapAsync`, `tapPromise`
264+
- Call methods: `callAsync`, `promise`
265+
266+
```js
267+
const hook = new AsyncParallelHook(["source"]);
268+
hook.tapPromise("Fetch", (src) => fetch(src));
269+
hook.tapAsync("Log", (src, cb) => {
270+
console.log("fetching", src);
271+
cb();
272+
});
273+
274+
await hook.promise("https://example.com");
275+
```
276+
277+
### AsyncParallelBailHook
278+
279+
Like `AsyncParallelHook`, but designed to bail out with a result. All tapped functions start in parallel; the first tap to produce a non-`undefined` value (synchronously, via its callback, or by resolving its promise) determines the hook’s result. The remaining taps continue to run but their results are ignored. Order is determined by tap registration order: an earlier tap’s value takes precedence over a later one’s, even if the later one finishes first.
280+
281+
- Tap methods: `tap`, `tapAsync`, `tapPromise`
282+
- Call methods: `callAsync`, `promise`
283+
284+
```js
285+
const hook = new AsyncParallelBailHook(["key"]);
286+
hook.tapPromise("Cache", async (key) => cache.get(key));
287+
hook.tapPromise("Db", async (key) => db.lookup(key));
288+
289+
const value = await hook.promise("user:42");
290+
// First non-undefined result (by registration order) wins.
291+
```
292+
293+
### AsyncSeriesHook
294+
295+
An asynchronous hook that runs tapped functions one after another, waiting for each to finish before starting the next. Results are ignored; `callAsync`'s callback is invoked with no result and `promise()` resolves to `undefined`. The first error aborts the series.
296+
297+
- Tap methods: `tap`, `tapAsync`, `tapPromise`
298+
- Call methods: `callAsync`, `promise`
299+
300+
```js
301+
const hook = new AsyncSeriesHook(["request"]);
302+
hook.tapPromise("Authenticate", async (req) => authenticate(req));
303+
hook.tapPromise("Log", async (req) => logger.info(req.url));
304+
305+
await hook.promise(request);
306+
```
307+
308+
### AsyncSeriesBailHook
309+
310+
An asynchronous series hook that allows exiting early. Tapped functions run one after another; as soon as one produces a non-`undefined` value, that value becomes the hook’s result and the remaining taps are skipped.
311+
312+
- Tap methods: `tap`, `tapAsync`, `tapPromise`
313+
- Call methods: `callAsync`, `promise`
314+
315+
```js
316+
const hook = new AsyncSeriesBailHook(["id"]);
317+
hook.tapPromise("Memory", async (id) => memory.get(id));
318+
hook.tapPromise("Disk", async (id) => disk.read(id));
319+
320+
const value = await hook.promise("doc-1");
321+
// Stops at the first tap that produces a value.
322+
```
323+
324+
### AsyncSeriesLoopHook
325+
326+
An asynchronous series hook that loops. Tapped functions run one after another; whenever a tap produces a non-`undefined` value the hook restarts from the first tap. The hook completes once a full pass yields `undefined` from every tap. The result is always `undefined`.
327+
328+
- Tap methods: `tap`, `tapAsync`, `tapPromise`
329+
- Call methods: `callAsync`, `promise`
330+
331+
```js
332+
const hook = new AsyncSeriesLoopHook(["job"]);
333+
hook.tapPromise("Process", async (job) => {
334+
const more = await job.step();
335+
if (more) return true; // restart the loop
336+
});
337+
338+
await hook.promise(job);
339+
```
340+
341+
### AsyncSeriesWaterfallHook
342+
343+
An asynchronous series hook that threads a value through its taps. The first argument passed to `callAsync` / `promise` is forwarded to the first tap. A tap's non-`undefined` return / callback / resolution value replaces it for the next tap; `undefined` keeps the previous value. The hook completes with the value after the last tap.
344+
345+
- Tap methods: `tap`, `tapAsync`, `tapPromise`
346+
- Call methods: `callAsync`, `promise`
347+
348+
```js
349+
const hook = new AsyncSeriesWaterfallHook(["source"]);
350+
hook.tapPromise("Read", async (src) => fs.readFile(src, "utf8"));
351+
hook.tapPromise("Trim", async (text) => text.trim());
352+
353+
const output = await hook.promise("./input.txt");
354+
```
355+
167356
## Interception
168357

169358
All Hooks offer an additional interception API:

0 commit comments

Comments
 (0)