浏览代码

feat: trigger edge param for debounce

Sv443 1 年之前
父节点
当前提交
a11ed77
共有 5 个文件被更改,包括 49 次插入11 次删除
  1. 5 0
      .changeset/grumpy-snakes-care.md
  2. 二进制
      .github/assets/debounce.png
  3. 1 1
      README-summary.md
  4. 27 6
      README.md
  5. 16 4
      lib/misc.ts

+ 5 - 0
.changeset/grumpy-snakes-care.md

@@ -0,0 +1,5 @@
+---
+"@sv443-network/userutils": minor
+---
+
+Added parameter to switch `debounce()` to trigger on the rising edge, instead of just the falling edge [(see docs)](https://github.com/Sv443-Network/UserUtils#debounce)

二进制
.github/assets/debounce.png


+ 1 - 1
README-summary.md

@@ -46,7 +46,7 @@ or view the documentation of previous major releases:
     - [DataStore](https://github.com/Sv443-Network/UserUtils#DataStore) - class that manages a sync & async persistent JSON database, including data migration
     - [autoPlural()](https://github.com/Sv443-Network/UserUtils#autoplural) - automatically pluralize a string
     - [pauseFor()](https://github.com/Sv443-Network/UserUtils#pausefor) - pause the execution of a function for a given amount of time
-    - [debounce()](https://github.com/Sv443-Network/UserUtils#debounce) - call a function only once, after a given amount of time
+    - [debounce()](https://github.com/Sv443-Network/UserUtils#debounce) - call a function only once in a series of calls, after or before a given timeout
     - [fetchAdvanced()](https://github.com/Sv443-Network/UserUtils#fetchadvanced) - wrapper around the fetch API with a timeout option
     - [insertValues()](https://github.com/Sv443-Network/UserUtils#insertvalues) - insert values into a string at specified placeholders
     - [compress()](https://github.com/Sv443-Network/UserUtils#compress) - compress a string with Gzip or Deflate

+ 27 - 6
README.md

@@ -47,7 +47,7 @@ View the documentation of previous major releases:
     - [DataStore](#datastore) - class that manages a sync & async persistent JSON database, including data migration
     - [autoPlural()](#autoplural) - automatically pluralize a string
     - [pauseFor()](#pausefor) - pause the execution of a function for a given amount of time
-    - [debounce()](#debounce) - call a function only once, after a given amount of time
+    - [debounce()](#debounce) - call a function only once in a series of calls, after or before a given timeout
     - [fetchAdvanced()](#fetchadvanced) - wrapper around the fetch API with a timeout option
     - [insertValues()](#insertvalues) - insert values into a string at specified placeholders
     - [compress()](#compress) - compress a string with Gzip or Deflate
@@ -1180,22 +1180,43 @@ async function run() {
 ### debounce()
 Usage:  
 ```ts
-debounce(func: Function, timeout?: number): Function
+debounce(func: Function, timeout?: number, edge?: "falling" | "rising"): Function
 ```
   
-Debounces a function, meaning that it will only be called once after a given amount of time.  
-This is very useful for functions that are called repeatedly, like event listeners, to remove extraneous calls.  
-All passed properties will be passed down to the debounced function.  
-The timeout will default to 300ms if left undefined.  
+Returns a debounced wrapper function, meaning that the given `func` will only be called once after or before a given amount of time.  
+This is very useful for functions that are called repeatedly, like event listeners, to remove a substantial amount of unnecessary calls.  
+All parameters passed to the returned function will be passed along to the input `func`  
+  
+The `timeout` will default to 300ms if left undefined.  
+  
+The `edge` ("falling" by default) determines if the function should be called after the timeout has passed or before it.  
+In simpler terms, this results in "falling" edge functions being called once at the very end of a sequence of calls, and "rising" edge functions being called once at the beginning and possibly multiple times following that, but at the very least they're spaced apart by what's passed in `timeout`.  
+  
+This diagram can hopefully help bring the difference across:  
+<details><summary><b>Click to view the diagram</b></summary>
+
+![debounce function edge diagram](./.github/assets/debounce.png)
+
+</details>
+
+<br>
   
 <details><summary><b>Example - click to view</b></summary>
 
 ```ts
 import { debounce } from "@sv443-network/userutils";
 
+// uses "falling" edge by default:
 window.addEventListener("resize", debounce((event) => {
   console.log("Window was resized:", event);
 }, 500)); // 500ms timeout
+
+// using "rising" edge:
+const myFunc = debounce((event) => {
+  console.log("Body was scrolled:", event);
+}, 100, "rising"); // 100ms timeout
+
+document.body.addEventListener("scroll", myFunc);
 ```
 
 </details>

+ 16 - 4
lib/misc.ts

@@ -53,12 +53,24 @@ export function pauseFor(time: number) {
 /**
  * Calls the passed {@linkcode func} after the specified {@linkcode timeout} in ms (defaults to 300).  
  * Any subsequent calls to this function will reset the timer and discard all previous calls.
+ * @param func The function to call after the timeout
+ * @param timeout The time in ms to wait before calling the function
+ * @param edge Whether to call the function at the very first call ("rising" edge) or the very last call ("falling" edge, default)
  */
-export function debounce<TFunc extends (...args: TArgs[]) => void, TArgs = any>(func: TFunc, timeout = 300) { // eslint-disable-line @typescript-eslint/no-explicit-any
-  let timer: number | undefined;
+export function debounce<TFunc extends (...args: TArgs[]) => void, TArgs = any>(func: TFunc, timeout = 300, edge: "rising" | "falling" = "falling") { // eslint-disable-line @typescript-eslint/no-explicit-any
+  let timer: NodeJS.Timeout | undefined;
+
   return function(...args: TArgs[]) {
-    clearTimeout(timer);
-    timer = setTimeout(() => func.apply(this, args), timeout) as unknown as number;
+    if(edge === "rising") {
+      if(!timer) {
+        func.apply(this, args);
+        timer = setTimeout(() => timer = undefined, timeout);
+      }
+    }
+    else {
+      clearTimeout(timer);
+      timer = setTimeout(() => func.apply(this, args), timeout);
+    }
   };
 }