Skip to content

Instantly share code, notes, and snippets.

@balazs-endresz
Created July 7, 2020 18:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save balazs-endresz/c0c814a513724f44d90265422b9ae530 to your computer and use it in GitHub Desktop.
Save balazs-endresz/c0c814a513724f44d90265422b9ae530 to your computer and use it in GitHub Desktop.
export function throttleDebouncePromise(
inner,
{ throttle, debounce, cache, immediateIfCached },
) {
// Ordinary debounce functions cause issues with promises and react-select but this works well.
// Based on: https://stackoverflow.com/questions/35228052/debounce-function-implemented-with-promises
const resolveCallbacks = []
const store = {}
let timeout = null
let time = new Date()
return (...args) => {
// Return cached value immediately if exists and if immediateIfCached is true
if (cache && immediateIfCached) {
const cacheKey = getCacheKey(...args)
if (Object.prototype.hasOwnProperty.call(store, cacheKey)) {
return new Promise((resolve) => resolve(store[cacheKey]))
}
}
const run = () => {
// Resolve only the last callback if there's any left
const resolve = resolveCallbacks.pop()
if (resolve) {
// Remove all the previous resolve callbacks
resolveCallbacks.splice(0, resolveCallbacks.length)
// Return cached value only now if exists and if immediateIfCached is false
if (cache && !immediateIfCached) {
const cacheKey = getCacheKey(...args)
if (Object.prototype.hasOwnProperty.call(store, cacheKey)) {
return resolve(store[cacheKey])
}
}
// If it wasn't cached then execute the inner function and cache when it resolves
const promise = inner(...args).then((result) => {
// Store in cache if caching is enabled
if (cache) {
store[getCacheKey(...args)] = result
}
return result
})
resolve(promise)
}
// Reset time for throttle
time = new Date()
return undefined
}
// E.g. if a key is pressed continously this will keep making requests
// and NOT wait until the key is released.
if (throttle && new Date() - time > throttle) {
run()
}
// When used with throttle this only ensures the last promise is always called.
// Without throttle it does what a debounce function is supposed to.
clearTimeout(timeout)
timeout = setTimeout(run, debounce)
// Return a promise each time the final debounced function is called.
// This promise will just store the resolve function in `resolveCallbacks`
// and when it's time we'll call the last one.
return new Promise((resolve) => {
resolveCallbacks.push(resolve)
})
}
}
function getCacheKey(...args) {
args.forEach((a) => {
if (
a !== null &&
!Number.isNaN(Number(a)) &&
!['string', 'number', 'boolean', 'undefined'].includes(typeof a)
) {
throw Error(`Can't create cache key for ${a} : ${typeof a}`)
}
})
return args.join('---')
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment