Browse Source

feat: queuedImmediate Debouncer done

Sv443 3 months ago
parent
commit
b490947b2e
1 changed files with 53 additions and 38 deletions
  1. 53 38
      lib/Debouncer.ts

+ 53 - 38
lib/Debouncer.ts

@@ -21,7 +21,8 @@ export type DebouncerType = "queuedImmediate" | "queuedIdle" | "discardingImmedi
 
 export type DebouncerFunc<TArgs> = (...args: TArgs[]) => void | unknown;
 
-export type DebouncerListener<TArgs> = [time: number, fn: DebouncerFunc<TArgs>];
+// #DEBUG
+let cli = 0;
 
 //#region class
 
@@ -39,13 +40,13 @@ export class Debouncer<TArgs> extends NanoEmitter<{
   change: (timeout: number, type: DebouncerType) => void;
 }> {
   /** All registered listener functions and the time they were attached */
-  protected listeners: DebouncerListener<TArgs>[] = [];
+  protected listeners: DebouncerFunc<TArgs>[] = [];
 
-  /** The latest call that was queued */
-  protected queuedListener: DebouncerListener<TArgs> | undefined = undefined;
+  /** The currently active timeout */
+  protected activeTimeout: ReturnType<typeof setTimeout> | undefined;
 
-  /** Whether the timeout is currently active */
-  protected timeoutActive = false;
+  /** The latest queued call */
+  protected queuedCall: DebouncerFunc<TArgs> | undefined;
 
   /**
    * Creates a new debouncer with the specified timeout and edge type.
@@ -60,12 +61,12 @@ export class Debouncer<TArgs> extends NanoEmitter<{
 
   /** Adds a listener function that will be called on timeout */
   public addListener(fn: DebouncerFunc<TArgs>) {
-    return this.listeners.push([Date.now(), fn]);
+    return this.listeners.push(fn);
   }
 
   /** Removes the listener with the specified function reference */
   public removeListener(fn: DebouncerFunc<TArgs>) {
-    const idx = this.listeners.findIndex((l) => l[1] === fn);
+    const idx = this.listeners.findIndex((l) => l === fn);
     idx !== -1 && this.listeners.splice(idx, 1);
   }
 
@@ -88,7 +89,7 @@ export class Debouncer<TArgs> extends NanoEmitter<{
 
   /** Whether the timeout is currently active, meaning any latest call to the {@linkcode call()} method will be queued */
   public isTimeoutActive() {
-    return this.timeoutActive;
+    return this.activeTimeout;
   }
 
   //#region type
@@ -107,39 +108,53 @@ export class Debouncer<TArgs> extends NanoEmitter<{
 
   /** Use this to call the debouncer with the specified arguments that will be passed to all listener functions registered with {@linkcode addListener()} */
   public call(...args: TArgs[]) {
-    const cl = () => {
-      this.emit("call", ...args);
-      this.timeoutActive = false;
-      this.queuedListener = undefined;
+    console.log(`  (call attempt ${cli})`);
+    cli++;
+
+    /** When called, calls all registered listeners */
+    const cl = (...a: TArgs[]) => {
+      this.queuedCall = undefined;
+      this.emit("call", ...a);
+      this.listeners.forEach((l) => l.apply(this, a));
     };
 
-    if(this.timeoutActive) {
-      this.queuedListener = [Date.now(), cl];
-      return;
-    }
-
-    this.timeoutActive = true;
-
-    switch(this.type) {
-    case "queuedImmediate":
-      cl();
-      setTimeout(() => this.timeoutActive = false, this.timeout);
-      break;
-    case "queuedIdle":
-      setTimeout(() => {
-        if(this.queuedListener) {
-          this.emit("call", ...args);
-          this.timeoutActive = false;
-          this.queuedListener = undefined;
+    /** Sets a timeout that will call the latest queued call and then set another timeout if there was a queued call */
+    const setPersistingTimeout = () => {
+      this.activeTimeout = setTimeout(() => {
+        if(this.queuedCall) {
+          this.queuedCall();
+          setPersistingTimeout();
         }
+        else
+          this.activeTimeout = undefined;
       }, this.timeout);
-      break;
-    case "discardingImmediate":
-      setTimeout(() => this.timeoutActive = false, this.timeout);
-      cl();
-      break;
-    default:
-      throw new Error(`Unknown debouncer edge type: ${this.type}`);
+    };
+
+    if(this.type === "queuedImmediate") {
+      if(typeof this.activeTimeout === "undefined") {
+        cl(...args);
+        setPersistingTimeout();
+      }
+      else
+        this.queuedCall = () => cl(...args);
+    }
+    // TODO: verify
+    else if(this.type === "queuedIdle") {
+      if(this.activeTimeout)
+        clearTimeout(this.activeTimeout);
+
+      this.activeTimeout = setTimeout(() => {
+        cl(...args);
+        this.activeTimeout = undefined;
+      }, this.timeout);
+    }
+    // TODO: verify
+    else if(this.type === "discardingImmediate") {
+      if(this.activeTimeout)
+        return;
+
+      cl(...args);
+      this.activeTimeout = setTimeout(() => this.activeTimeout = undefined, this.timeout);
     }
   }
 }