Created
August 11, 2020 16:53
-
-
Save falahati/fda618a9b59bb7d7f33b9ba0d5ef01a3 to your computer and use it in GitHub Desktop.
Typescript type-safe debounce and throttle extension methods
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export { }; | |
type GenericFunction = (...params: any[]) => any; | |
type PromisifiedFunction<T extends GenericFunction> = (...params: Parameters<T>) => Promise<ReturnType<T>>; | |
declare global { | |
// tslint:disable-next-line:interface-name | |
interface Function { | |
leadingDebounce<T extends GenericFunction>(this: T, wait: number): PromisifiedFunction<T>; | |
trailingDebounce<T extends GenericFunction>(this: T, wait: number): PromisifiedFunction<T>; | |
leadingThrottle<T extends GenericFunction>(this: T, wait: number): PromisifiedFunction<T>; | |
trailingThrottle<T extends GenericFunction>(this: T, wait: number): PromisifiedFunction<T>; | |
} | |
} | |
const limiter = <T extends GenericFunction>(func: T, wait: number, leading: boolean, throttle: boolean): PromisifiedFunction<T> => { | |
let timeout: number | undefined; | |
// for leading | |
let savedResult: any; | |
let savedError: any; | |
let wasSuccessful: boolean = false; | |
// for trailling | |
let resolveCallbacks: Array<(result: any) => void> = []; | |
let rejectCallbacks: Array<(error: any) => void> = []; | |
return function(this: any) { | |
const context = this; | |
const args = arguments as any; | |
return new Promise( | |
(resolve, reject) => { | |
const delayedCallback = () => { | |
timeout = undefined; | |
// cleanup leading variables | |
savedResult = undefined; | |
savedError = undefined; | |
wasSuccessful = false; | |
if (!leading) { | |
// if trailing, execute the function now and notify previous callers | |
try { | |
const result = func.apply(context, args); | |
for (const callback of resolveCallbacks) { | |
callback(result); | |
} | |
} catch (error) { | |
for (const callback of rejectCallbacks) { | |
callback(error); | |
} | |
} | |
} | |
// cleanup trailing variables | |
resolveCallbacks = []; | |
rejectCallbacks = []; | |
}; | |
if (leading && !timeout) { | |
// if leading and if this is the first call: | |
// 1. execute the function and keep the results for next calls | |
try { | |
savedResult = func.apply(context, args); | |
wasSuccessful = true; | |
resolve(savedResult); | |
} catch (err) { | |
savedError = err; | |
wasSuccessful = false; | |
reject(savedError); | |
} | |
} else if (leading) { | |
// if leading, but this is a duplicate call: | |
// 1. extend the delay if necesserly | |
// 2. report back the last result immediately | |
if (!throttle) { | |
window.clearTimeout(timeout); | |
} | |
if (wasSuccessful) { | |
resolve(savedResult); | |
} else { | |
reject(savedError); | |
} | |
} else { | |
// if trailing: | |
// 1. delay the execution if necesserly | |
// 2. subscribe to it's eventual result | |
resolveCallbacks.push(resolve); | |
rejectCallbacks.push(reject); | |
if (!throttle && timeout) { | |
window.clearTimeout(timeout); | |
} | |
} | |
// Extend or delay the execution only if: | |
// 1. this is not throttle limitter | |
// OR | |
// 2. when the throttle limitation already passed | |
if (!throttle || !timeout) { | |
timeout = window.setTimeout(delayedCallback, wait); | |
} | |
}, | |
); | |
}; | |
}; | |
Function.prototype.leadingDebounce = function(this: any, wait: number) { | |
return limiter(this, wait, true, false); | |
}; | |
Function.prototype.trailingDebounce = function(this: any, wait: number) { | |
return limiter(this, wait, false, false); | |
}; | |
Function.prototype.leadingThrottle = function(this: any, wait: number) { | |
return limiter(this, wait, true, true); | |
}; | |
Function.prototype.trailingThrottle = function(this: any, wait: number) { | |
return limiter(this, wait, false, true); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment