Skip to content

Instantly share code, notes, and snippets.

@adnanalbeda
Last active March 4, 2023 18:39
Show Gist options
  • Save adnanalbeda/55ba6d0995de21cc115da0ade3aa6be3 to your computer and use it in GitHub Desktop.
Save adnanalbeda/55ba6d0995de21cc115da0ade3aa6be3 to your computer and use it in GitHub Desktop.
Vanilla TS Utils
// limit execution of a function to one per x amount of time to the last interaction.
// Set 'immediate' to true to run the function then it waits for x time of no interaction.
// Keep it undefined or false for default behavior, which is to execute after x time no interaction.
export default function debounce(
func: () => void,
wait: number,
immediate?: boolean
) {
// 'private' variable for instance
// The returned function will be able to reference this due to closure.
// Each call to the returned function will share this common timer.
let timeout: number | null;
// Calling debounce returns a new anonymous function
return function () {
// reference the context and args for the setTimeout function
const context = this,
args = arguments;
// Should the function be called now? If immediate is true
// and not already in a timeout then the answer is: Yes
const callNow = immediate && !timeout;
// This is the basic debounce behavior where you can call this
// function several times, but it will only execute once
// (before or after imposing a delay).
// Each time the returned function is called, the timer starts over.
if (timeout != null) clearTimeout(timeout);
// Set the new timeout
timeout = setTimeout(function () {
// Inside the timeout function, clear the timeout variable
// which will let the next execution run when in 'immediate' mode
timeout = null;
// Check if the function already ran with the immediate flag
if (!immediate) {
// Call the original function with apply
// apply lets you define the 'this' object as well as the arguments
// (both captured before setTimeout)
func.apply(context, args);
}
}, wait);
// Immediate mode and no wait timer? Execute the function...
if (callNow) func.apply(context, args);
};
}
type DeferredAsync = {
promise: Promise<unknown> | null;
cancel: (reason?: unknown) => void;
};
export function deferredAsync(ms: number): DeferredAsync {
let timeout,
cancel,
promise = new Promise((resolve, reject) => {
timeout = setTimeout(resolve, ms);
cancel = (reason?: any) => {
reject(reason ?? "CANCELED");
clearTimeout(timeout);
};
});
return { promise, cancel };
}
export function debounceAsync<TParam>(task: (params: TParam) => Promise<unknown>, ms: number) {
let t: DeferredAsync = { promise: null, cancel: () => void 0 };
return async (params: TParam) => {
try {
t.cancel();
t = deferredAsync(ms);
await t.promise;
await task(params);
} catch (_) {
/* prevent memory leak */
}
};
}
// this function limit execution of a function to one per x amount of time
export function throttle(func: () => void, wait: number) {
// 'private' variable for instance
// The returned function will be able to reference this due to closure.
// Each call to the returned function will share this common timer.
let timeout: number | null = null;
// Calling debounce returns a new anonymous function
return function () {
// reference the context and args for the setTimeout function
const context = this,
args = arguments;
if (!timeout) {
// No timeout means you must execute the function...
func.apply(context, args);
timeout = setTimeout(function () {
// Inside the timeout function, clear the timeout variable
// which will let allow fpr the next execution to run
timeout = null;
}, wait);
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment