git-watchtower 2.0.1 → 2.0.3

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/utils/async.js +37 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-watchtower",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "Terminal-based Git branch monitor with activity sparklines and optional dev server with live reload",
5
5
  "main": "bin/git-watchtower.js",
6
6
  "bin": {
@@ -175,6 +175,18 @@ function sleep(ms) {
175
175
 
176
176
  /**
177
177
  * Debounce a function - only execute after delay with no calls
178
+ *
179
+ * cancel() both clears the pending timer and sets an internal flag
180
+ * that the scheduled callback checks at the top. Without the flag,
181
+ * cancel() racing with a timer whose callback has already been
182
+ * dequeued (the timer fired, but the callback hadn't executed yet)
183
+ * was a no-op — clearTimeout can't recall a callback that libuv
184
+ * has already promoted — so fn would run anyway and write into
185
+ * state the caller had just asked to abandon.
186
+ *
187
+ * Calling debounced(...) re-arms: the cancelled flag resets so a new
188
+ * schedule is not pre-squelched by a prior cancel().
189
+ *
178
190
  * @template {(...args: any[]) => void} T
179
191
  * @param {T} fn - Function to debounce
180
192
  * @param {number} delay - Delay in milliseconds
@@ -182,18 +194,22 @@ function sleep(ms) {
182
194
  */
183
195
  function debounce(fn, delay) {
184
196
  let timeoutId = null;
197
+ let cancelled = false;
185
198
 
186
199
  const debounced = (...args) => {
187
200
  if (timeoutId) {
188
201
  clearTimeout(timeoutId);
189
202
  }
203
+ cancelled = false;
190
204
  timeoutId = setTimeout(() => {
191
- fn(...args);
192
205
  timeoutId = null;
206
+ if (cancelled) return;
207
+ fn(...args);
193
208
  }, delay);
194
209
  };
195
210
 
196
211
  debounced.cancel = () => {
212
+ cancelled = true;
197
213
  if (timeoutId) {
198
214
  clearTimeout(timeoutId);
199
215
  timeoutId = null;
@@ -206,17 +222,24 @@ function debounce(fn, delay) {
206
222
 
207
223
  /**
208
224
  * Throttle a function - execute at most once per interval
225
+ *
226
+ * Mirrors debounce() in returning a wrapped function with a `.cancel()`
227
+ * method. If a trailing call has been scheduled (throttle fires on the
228
+ * leading edge and coalesces further calls during the interval into a
229
+ * single trailing call), cancel() drops it. Wire cancel() into shutdown
230
+ * paths so a UI-event-driven throttle can't fire a deferred call after
231
+ * the target module has torn down and leave state partially written.
232
+ *
209
233
  * @template {(...args: any[]) => void} T
210
234
  * @param {T} fn - Function to throttle
211
235
  * @param {number} interval - Minimum interval between calls in milliseconds
212
- * @returns {T}
236
+ * @returns {T & { cancel: () => void }}
213
237
  */
214
238
  function throttle(fn, interval) {
215
239
  let lastCall = 0;
216
240
  let timeoutId = null;
217
241
 
218
- // @ts-ignore - TypeScript can't verify generic function return type
219
- return (...args) => {
242
+ const throttled = (...args) => {
220
243
  const now = Date.now();
221
244
  const timeSinceLastCall = now - lastCall;
222
245
 
@@ -232,6 +255,16 @@ function throttle(fn, interval) {
232
255
  }, interval - timeSinceLastCall);
233
256
  }
234
257
  };
258
+
259
+ throttled.cancel = () => {
260
+ if (timeoutId) {
261
+ clearTimeout(timeoutId);
262
+ timeoutId = null;
263
+ }
264
+ };
265
+
266
+ // @ts-ignore - TypeScript can't verify generic function augmentation
267
+ return throttled;
235
268
  }
236
269
 
237
270
  module.exports = {