@hardlydifficult/poller 1.0.6 → 1.0.8
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 +76 -136
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hardlydifficult/poller
|
|
2
2
|
|
|
3
|
-
A generic polling utility
|
|
3
|
+
A lightweight, generic polling utility with debounced triggers, overlapping request handling, and deep equality change detection.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -13,192 +13,132 @@ npm install @hardlydifficult/poller
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { Poller } from "@hardlydifficult/poller";
|
|
15
15
|
|
|
16
|
-
//
|
|
16
|
+
// Define a fetch function (e.g., API call)
|
|
17
|
+
const fetchUser = async () => {
|
|
18
|
+
const res = await fetch("https://api.example.com/user");
|
|
19
|
+
return res.json();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Create and start the poller
|
|
17
23
|
const poller = new Poller(
|
|
18
|
-
|
|
19
|
-
const response = await fetch("https://api.example.com/status");
|
|
20
|
-
return await response.json();
|
|
21
|
-
},
|
|
24
|
+
fetchUser,
|
|
22
25
|
(current, previous) => {
|
|
23
|
-
console.log("
|
|
26
|
+
console.log("User updated:", current);
|
|
24
27
|
},
|
|
25
|
-
5000
|
|
26
|
-
(error) => {
|
|
27
|
-
console.error("Polling error:", error);
|
|
28
|
-
}
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
await poller.start(); // Begins polling immediately
|
|
32
|
-
// ... later
|
|
33
|
-
poller.stop(); // Stops polling
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### Basic Usage
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
import { Poller } from "@hardlydifficult/poller";
|
|
40
|
-
|
|
41
|
-
const poller = new Poller(
|
|
42
|
-
async () => await fetchCurrentState(),
|
|
43
|
-
(current, previous) => console.log("State changed!", current),
|
|
44
|
-
5 * 60 * 1000 // poll every 5 minutes
|
|
28
|
+
5000 // Poll every 5 seconds
|
|
45
29
|
);
|
|
46
30
|
|
|
47
31
|
await poller.start();
|
|
32
|
+
// Polls every 5s, fires onChange only when JSON data changes
|
|
48
33
|
|
|
49
|
-
//
|
|
50
|
-
poller.trigger(1000);
|
|
34
|
+
// Manually trigger a debounced poll (e.g., after user action)
|
|
35
|
+
poller.trigger(1000); // Waits 1s, then polls once
|
|
51
36
|
|
|
52
|
-
// Stop polling
|
|
37
|
+
// Stop polling when no longer needed
|
|
53
38
|
poller.stop();
|
|
54
39
|
```
|
|
55
40
|
|
|
56
|
-
## Polling
|
|
57
|
-
|
|
58
|
-
The `Poller` class fetches data at a configurable interval and invokes a callback when the result changes. It fetches immediately on `start()`, then at the configured interval.
|
|
41
|
+
## Polling Lifecycle
|
|
59
42
|
|
|
60
|
-
|
|
61
|
-
import { Poller } from "@hardlydifficult/poller";
|
|
43
|
+
### Start and Stop
|
|
62
44
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const response = await fetch("/api/status");
|
|
66
|
-
return response.json();
|
|
67
|
-
},
|
|
68
|
-
(current, previous) => {
|
|
69
|
-
console.log("Previous:", previous);
|
|
70
|
-
console.log("Current:", current);
|
|
71
|
-
},
|
|
72
|
-
10000 // poll every 10 seconds
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
await poller.start();
|
|
76
|
-
// Fetches immediately, then every 10 seconds
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### `start()`
|
|
80
|
-
|
|
81
|
-
Starts polling. Fetches immediately, then at the configured interval. Calling `start()` multiple times is safe (idempotent).
|
|
45
|
+
- `start()`: Begins polling at the configured interval. Idempotent—safe to call multiple times.
|
|
46
|
+
- `stop()`: Cancels timers and clears any pending debounced trigger. Safe to call multiple times.
|
|
82
47
|
|
|
83
48
|
```typescript
|
|
84
|
-
await poller.start();
|
|
85
|
-
await poller.start(); // No-op
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### `stop()`
|
|
89
|
-
|
|
90
|
-
Stops polling and cleans up all timers.
|
|
49
|
+
await poller.start();
|
|
50
|
+
await poller.start(); // No-op
|
|
91
51
|
|
|
92
|
-
```typescript
|
|
93
52
|
poller.stop();
|
|
94
|
-
// No
|
|
53
|
+
poller.stop(); // No-op
|
|
95
54
|
```
|
|
96
55
|
|
|
97
56
|
## Change Detection
|
|
98
57
|
|
|
99
|
-
|
|
58
|
+
### Deep Equality via JSON
|
|
100
59
|
|
|
101
|
-
|
|
102
|
-
- On subsequent polls, `onChange(current, previous)` is invoked only if the new value differs structurally.
|
|
60
|
+
Change detection uses `JSON.stringify()` comparison, enabling structural equality checks for objects and arrays—even when references differ.
|
|
103
61
|
|
|
104
62
|
```typescript
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
console.log("Changed from", previous, "to", current);
|
|
110
|
-
},
|
|
111
|
-
5000
|
|
112
|
-
);
|
|
63
|
+
const fetchFn = async () => ({ items: [1, 2, 3] });
|
|
64
|
+
const onChange = (current, previous) => {
|
|
65
|
+
// Fires only when structure changes
|
|
66
|
+
};
|
|
113
67
|
|
|
114
|
-
|
|
68
|
+
const poller = new Poller(fetchFn, onChange, 1000);
|
|
69
|
+
// Even if new object reference returned, onChange won’t fire unless JSON differs
|
|
115
70
|
```
|
|
116
71
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Call `trigger()` to manually poll immediately, with optional debouncing. Multiple rapid triggers are coalesced into a single poll.
|
|
120
|
-
|
|
121
|
-
- Accepts a `debounceMs` parameter (default: `1000`).
|
|
122
|
-
- Multiple rapid calls reset the debounce timer — only the last trigger fires.
|
|
123
|
-
- No-op if the poller is not running.
|
|
72
|
+
### On Change Callback
|
|
124
73
|
|
|
125
74
|
```typescript
|
|
126
|
-
|
|
127
|
-
async () => await fetchData(),
|
|
128
|
-
(current, previous) => console.log("Updated:", current),
|
|
129
|
-
60000
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
await poller.start();
|
|
133
|
-
|
|
134
|
-
// Trigger a poll with 1000ms debounce (default)
|
|
135
|
-
poller.trigger();
|
|
136
|
-
|
|
137
|
-
// Trigger with custom debounce
|
|
138
|
-
poller.trigger(500);
|
|
139
|
-
|
|
140
|
-
// Multiple rapid triggers coalesce into one poll
|
|
141
|
-
poller.trigger(500);
|
|
142
|
-
poller.trigger(500);
|
|
143
|
-
poller.trigger(500); // Only one poll fires after 500ms
|
|
75
|
+
onChange(current: T, previous: T | undefined): void
|
|
144
76
|
```
|
|
145
77
|
|
|
78
|
+
- `current`: Most recently fetched value
|
|
79
|
+
- `previous`: Prior value, or `undefined` on first poll
|
|
80
|
+
|
|
146
81
|
## Error Handling
|
|
147
82
|
|
|
148
|
-
|
|
149
|
-
|
|
83
|
+
### Optional Error Callback
|
|
84
|
+
|
|
85
|
+
Provide an `onError` handler to manage fetch failures; polling continues regardless.
|
|
150
86
|
|
|
151
87
|
```typescript
|
|
152
88
|
const poller = new Poller(
|
|
153
|
-
|
|
154
|
-
|
|
89
|
+
fetchFn,
|
|
90
|
+
onChange,
|
|
155
91
|
5000,
|
|
156
92
|
(error) => {
|
|
157
|
-
console.
|
|
158
|
-
// Polling continues automatically
|
|
93
|
+
console.warn("Polling error:", error);
|
|
159
94
|
}
|
|
160
95
|
);
|
|
161
|
-
|
|
162
|
-
await poller.start();
|
|
163
96
|
```
|
|
164
97
|
|
|
165
|
-
|
|
98
|
+
- Errors do not halt the polling interval.
|
|
99
|
+
- If `onError` is omitted, errors are silently suppressed.
|
|
100
|
+
|
|
101
|
+
## Manual Triggering
|
|
102
|
+
|
|
103
|
+
### Debounced Triggers
|
|
166
104
|
|
|
167
|
-
|
|
105
|
+
- `trigger(debounceMs?: number)`: Schedules a one-time poll after a debounce delay (default: 1000ms).
|
|
106
|
+
- Multiple rapid calls cancel previous timeouts—only the last one fires.
|
|
168
107
|
|
|
169
|
-
|
|
108
|
+
```typescript
|
|
109
|
+
poller.trigger(2000); // Polls in 2s
|
|
110
|
+
poller.trigger(2000); // Cancelled, re-schedule to 2s from now
|
|
111
|
+
poller.trigger(2000); // Cancelled again, final delay applies
|
|
112
|
+
```
|
|
170
113
|
|
|
171
|
-
-
|
|
172
|
-
- `stop()`: Clears the polling timer and any pending debounced triggers. No-op if already stopped.
|
|
114
|
+
- No-op if `poller` is not running.
|
|
173
115
|
|
|
174
|
-
|
|
116
|
+
## Overlap Handling
|
|
175
117
|
|
|
176
|
-
|
|
177
|
-
-
|
|
118
|
+
- Concurrent fetches are skipped—only one in-flight request is allowed at a time.
|
|
119
|
+
- Prevents resource waste and race conditions during slow network calls.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Interval fires every 5s; if fetch takes 6s:
|
|
123
|
+
// - Second interval fire is skipped
|
|
124
|
+
// - Third interval fire executes after first completes
|
|
125
|
+
```
|
|
178
126
|
|
|
179
127
|
## API Reference
|
|
180
128
|
|
|
181
|
-
### `Poller<T>`
|
|
129
|
+
### `Poller<T>`
|
|
182
130
|
|
|
183
|
-
| Parameter
|
|
184
|
-
|
|
185
|
-
| `fetchFn`
|
|
186
|
-
| `onChange`
|
|
187
|
-
| `intervalMs
|
|
188
|
-
| `onError?`
|
|
131
|
+
| Parameter | Type | Description |
|
|
132
|
+
|-----------|------|-------------|
|
|
133
|
+
| `fetchFn` | `() => Promise<T>` | Async function to fetch data |
|
|
134
|
+
| `onChange` | `(current: T, previous: T \| undefined) => void` | Callback on value change |
|
|
135
|
+
| `intervalMs` | `number` | Polling interval in milliseconds |
|
|
136
|
+
| `onError?` | `(error: unknown) => void` | Optional error handler |
|
|
189
137
|
|
|
190
138
|
### Methods
|
|
191
139
|
|
|
192
|
-
| Method
|
|
193
|
-
|
|
194
|
-
| `start
|
|
195
|
-
| `stop
|
|
196
|
-
| `trigger
|
|
197
|
-
|
|
198
|
-
### Behavior
|
|
199
|
-
|
|
200
|
-
- **Deep equality** — uses JSON serialization to detect structural changes
|
|
201
|
-
- **Overlap prevention** — skips a poll if the previous fetch is still running
|
|
202
|
-
- **Error resilience** — continues polling after fetch errors
|
|
203
|
-
- **Idempotent start** — calling `start()` multiple times is safe
|
|
204
|
-
- **Debounced triggers** — multiple `trigger()` calls coalesce into one poll
|
|
140
|
+
| Method | Signature | Description |
|
|
141
|
+
|--------|-----------|-------------|
|
|
142
|
+
| `start` | `(): Promise<void>` | Begin polling immediately and then at `intervalMs` |
|
|
143
|
+
| `stop` | `(): void` | Cancel all timers and pending triggers |
|
|
144
|
+
| `trigger` | `(debounceMs?: number) => void` | Trigger a one-time poll after debounce delay |
|