@hardlydifficult/poller 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -206
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hardlydifficult/poller
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A lightweight polling utility with debounced triggers, overlapping request handling, and deep equality change detection.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -13,246 +13,81 @@ npm install @hardlydifficult/poller
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { Poller } from "@hardlydifficult/poller";
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
},
|
|
21
|
-
(current, previous) => {
|
|
22
|
-
console.log("Status changed:", current);
|
|
23
|
-
},
|
|
24
|
-
5000 // poll every 5 seconds
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
await poller.start();
|
|
28
|
-
// Polls start immediately
|
|
16
|
+
const fetchFn = async () => {
|
|
17
|
+
const response = await fetch("https://api.example.com/data");
|
|
18
|
+
return response.json();
|
|
19
|
+
};
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
|
|
21
|
+
const onChange = (current: unknown, previous: unknown) => {
|
|
22
|
+
console.log("Data changed:", current);
|
|
23
|
+
};
|
|
32
24
|
|
|
33
|
-
|
|
34
|
-
poller.stop();
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Basic Usage
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
import { Poller } from "@hardlydifficult/poller";
|
|
41
|
-
|
|
42
|
-
const poller = new Poller(
|
|
43
|
-
async () => await fetchCurrentState(),
|
|
44
|
-
(current, previous) => console.log("State changed!", current),
|
|
45
|
-
5 * 60 * 1000 // poll every 5 minutes
|
|
46
|
-
);
|
|
25
|
+
const poller = new Poller(fetchFn, onChange, 5000);
|
|
47
26
|
|
|
48
27
|
await poller.start();
|
|
28
|
+
// => Starts polling every 5 seconds, firing onChange on first fetch and any change
|
|
49
29
|
|
|
50
|
-
// Manual trigger with debounce (e.g. from a webhook)
|
|
51
30
|
poller.trigger(1000);
|
|
52
|
-
|
|
53
|
-
// Stop polling
|
|
54
|
-
poller.stop();
|
|
31
|
+
// => Triggers a debounced poll after 1 second (overriding any previous trigger call)
|
|
55
32
|
```
|
|
56
33
|
|
|
57
|
-
##
|
|
58
|
-
|
|
59
|
-
The `Poller` executes a fetch function at a fixed interval and invokes a callback only when the returned value changes.
|
|
34
|
+
## Poller Class
|
|
60
35
|
|
|
61
|
-
|
|
62
|
-
- Fetches at the configured `intervalMs`
|
|
63
|
-
- Skips overlapping fetches (if a previous fetch is still in progress)
|
|
36
|
+
A generic polling utility that periodically fetches data and invokes a callback when the result changes.
|
|
64
37
|
|
|
65
|
-
|
|
66
|
-
const poller = new Poller(
|
|
67
|
-
async () => fetchJson("/api/data"),
|
|
68
|
-
(current, previous) => console.log("Changed:", current),
|
|
69
|
-
10_000
|
|
70
|
-
);
|
|
71
|
-
await poller.start();
|
|
72
|
-
```
|
|
38
|
+
### Constructor
|
|
73
39
|
|
|
74
|
-
|
|
40
|
+
| Parameter | Type | Description |
|
|
41
|
+
|--------------|-----------------------------------|----------------------------------------------|
|
|
42
|
+
| `fetchFn` | `() => Promise<T>` | Async function that fetches the latest data |
|
|
43
|
+
| `onChange` | `(current: T, previous: T | undefined) => void` | Callback invoked when data changes |
|
|
44
|
+
| `intervalMs` | `number` | Polling interval in milliseconds |
|
|
45
|
+
| `onError?` | `(error: unknown) => void` | Optional callback invoked on fetch errors |
|
|
75
46
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
const poller = new Poller(
|
|
80
|
-
async () => ({ items: [1, 2, 3] }),
|
|
81
|
-
(current, previous) => console.log("Changed:", current),
|
|
82
|
-
1000
|
|
83
|
-
);
|
|
47
|
+
### Methods
|
|
84
48
|
|
|
85
|
-
|
|
86
|
-
await poller.start();
|
|
87
|
-
// onChange fires only when structure changes
|
|
88
|
-
```
|
|
49
|
+
#### `start(): Promise<void>`
|
|
89
50
|
|
|
90
|
-
|
|
91
|
-
- On subsequent polls, `onChange(current, previous)` is invoked only if the new value differs structurally.
|
|
51
|
+
Starts polling at the configured interval. Does nothing if already running.
|
|
92
52
|
|
|
93
53
|
```typescript
|
|
94
|
-
const poller = new Poller(
|
|
95
|
-
async () => ({ count: 5, items: [1, 2, 3] }),
|
|
96
|
-
(current, previous) => {
|
|
97
|
-
// Only fires when the JSON representation changes
|
|
98
|
-
console.log("Changed from", previous, "to", current);
|
|
99
|
-
},
|
|
100
|
-
5000
|
|
101
|
-
);
|
|
102
|
-
|
|
103
54
|
await poller.start();
|
|
55
|
+
// => Begins polling; invokes onChange immediately on first fetch
|
|
104
56
|
```
|
|
105
57
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
Polling can be started and stopped at any time. The `stop()` method cancels both the periodic timer and any pending debounced triggers.
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
await poller.start(); // begins polling
|
|
112
|
-
poller.stop(); // cancels all timers, stops future polls
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### `start()`
|
|
116
|
-
|
|
117
|
-
Starts polling. Fetches immediately, then at the configured interval. Calling `start()` multiple times is safe (idempotent).
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
await poller.start(); // Fetches immediately
|
|
121
|
-
await poller.start(); // No-op, already running
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### `stop()`
|
|
58
|
+
#### `stop(): void`
|
|
125
59
|
|
|
126
|
-
Stops polling and
|
|
60
|
+
Stops polling and clears all timers (including any pending debounced trigger).
|
|
127
61
|
|
|
128
62
|
```typescript
|
|
129
63
|
poller.stop();
|
|
130
|
-
//
|
|
64
|
+
// => Immediately halts polling and cancels pending triggers
|
|
131
65
|
```
|
|
132
66
|
|
|
133
|
-
|
|
67
|
+
#### `trigger(debounceMs = 1000): void`
|
|
134
68
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
| Parameter | Type | Default | Description |
|
|
138
|
-
|-------------|----------|---------|----------------------------------------|
|
|
139
|
-
| `debounceMs`| `number` | `1000` | Milliseconds to wait before polling |
|
|
69
|
+
Triggers a debounced poll. Multiple rapid calls reset the debounce timer.
|
|
140
70
|
|
|
141
71
|
```typescript
|
|
142
|
-
//
|
|
143
|
-
poller.trigger();
|
|
144
|
-
|
|
145
|
-
// Custom debounce
|
|
146
|
-
poller.trigger(500);
|
|
147
|
-
|
|
148
|
-
// If multiple triggers occur within debounceMs, only the last one fires
|
|
149
|
-
poller.trigger();
|
|
150
|
-
poller.trigger();
|
|
151
|
-
poller.trigger(); // only this one will poll after 1s
|
|
72
|
+
poller.trigger(500); // Debounce: 500 ms
|
|
73
|
+
poller.trigger(200); // Resets to 200 ms from now
|
|
74
|
+
// Only one poll fires after the last trigger delay
|
|
152
75
|
```
|
|
153
76
|
|
|
154
77
|
### Behavior
|
|
155
78
|
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
const poller = new Poller(
|
|
162
|
-
async () => await fetchData(),
|
|
163
|
-
(current, previous) => console.log("Updated:", current),
|
|
164
|
-
60000
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
await poller.start();
|
|
168
|
-
|
|
169
|
-
// Trigger a poll with 1000ms debounce (default)
|
|
170
|
-
poller.trigger();
|
|
171
|
-
|
|
172
|
-
// Trigger with custom debounce
|
|
173
|
-
poller.trigger(500);
|
|
174
|
-
|
|
175
|
-
// Multiple rapid triggers coalesce into one poll
|
|
176
|
-
poller.trigger(500);
|
|
177
|
-
poller.trigger(500);
|
|
178
|
-
poller.trigger(500); // Only one poll fires after 500ms
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## Error Handling
|
|
182
|
-
|
|
183
|
-
Fetch errors do not halt polling. An optional `onError` callback receives any errors, and polling continues at the next interval.
|
|
184
|
-
|
|
185
|
-
```typescript
|
|
186
|
-
const poller = new Poller(
|
|
187
|
-
async () => {
|
|
188
|
-
if (Math.random() < 0.5) throw new Error("Network fail");
|
|
189
|
-
return "ok";
|
|
190
|
-
},
|
|
191
|
-
(current) => console.log(current),
|
|
192
|
-
1000,
|
|
193
|
-
(error) => console.error("Fetch failed:", error)
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
await poller.start();
|
|
197
|
-
// Continues polling even after errors
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### Error Handling Details
|
|
201
|
-
|
|
202
|
-
- Errors during fetch are caught and passed to the optional `onError` callback.
|
|
203
|
-
- Polling continues after errors — no automatic retry logic is applied.
|
|
79
|
+
- **Deep equality detection**: Uses `JSON.stringify` to detect structural changes, even with new object references.
|
|
80
|
+
- **Overlapping request handling**: Skips polls while a fetch is in progress.
|
|
81
|
+
- **Error resilience**: Continues polling on errors if `onError` is provided; otherwise, errors are silently ignored.
|
|
204
82
|
|
|
205
83
|
```typescript
|
|
206
84
|
const poller = new Poller(
|
|
207
|
-
async () =>
|
|
208
|
-
(current, previous) => console.log("
|
|
209
|
-
|
|
210
|
-
(error) =>
|
|
211
|
-
console.error("Fetch failed:", error);
|
|
212
|
-
// Polling continues automatically
|
|
213
|
-
}
|
|
85
|
+
async () => fetch("https://api.example.com/status").then(r => r.json()),
|
|
86
|
+
(current, previous) => console.log("Status changed:", current),
|
|
87
|
+
10000, // 10 seconds
|
|
88
|
+
(error) => console.error("Polling error:", error)
|
|
214
89
|
);
|
|
215
90
|
|
|
216
91
|
await poller.start();
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
## Core Features
|
|
220
|
-
|
|
221
|
-
### Polling Lifecycle
|
|
222
|
-
|
|
223
|
-
The `Poller` manages its lifecycle with `start()` and `stop()` methods.
|
|
224
|
-
|
|
225
|
-
- `start()`: Begins polling immediately and then at the configured interval. Idempotent — calling it multiple times has no effect after the first call.
|
|
226
|
-
- `stop()`: Clears the polling timer and any pending debounced triggers. No-op if already stopped.
|
|
227
|
-
|
|
228
|
-
### Concurrent Fetch Handling
|
|
229
|
-
|
|
230
|
-
Overlapping fetches (e.g., due to slow network) are automatically skipped:
|
|
231
|
-
- If `poll()` is already running when the interval fires, the next poll is skipped until the current one completes.
|
|
232
|
-
|
|
233
|
-
## API Reference
|
|
234
|
-
|
|
235
|
-
### `Poller<T>` Constructor
|
|
236
|
-
|
|
237
|
-
| Parameter | Type | Description |
|
|
238
|
-
|-------------|---------------------------------------|------------------------------------------|
|
|
239
|
-
| `fetchFn` | `() => Promise<T>` | Async function returning the current state |
|
|
240
|
-
| `onChange` | `(current: T, previous: T | undefined) => void` | Callback invoked on state changes |
|
|
241
|
-
| `intervalMs`| `number` | Polling interval in milliseconds |
|
|
242
|
-
| `onError?` | `(error: unknown) => void` | Optional callback for fetch errors |
|
|
243
|
-
|
|
244
|
-
### Methods
|
|
245
|
-
|
|
246
|
-
| Method | Signature | Description |
|
|
247
|
-
|--------------|----------------------------------------|----------------------------------------------|
|
|
248
|
-
| `start()` | `(): Promise<void>` | Begins polling (immediate + interval-based) |
|
|
249
|
-
| `stop()` | `(): void` | Stops polling and clears timers |
|
|
250
|
-
| `trigger()` | `(debounceMs?: number) => void` | Triggers a debounced manual poll |
|
|
251
|
-
|
|
252
|
-
### Behavior
|
|
253
|
-
|
|
254
|
-
- **Deep equality** — uses JSON serialization to detect structural changes
|
|
255
|
-
- **Overlap prevention** — skips a poll if the previous fetch is still running
|
|
256
|
-
- **Error resilience** — continues polling after fetch errors
|
|
257
|
-
- **Idempotent start** — calling `start()` multiple times is safe
|
|
258
|
-
- **Debounced triggers** — multiple `trigger()` calls coalesce into one poll
|
|
92
|
+
poller.trigger(500); // Manual override after 0.5s if triggered again
|
|
93
|
+
```
|