Skip to content

Instantly share code, notes, and snippets.

@srph
Last active August 28, 2024 08:27
Show Gist options
  • Save srph/ac8196935fb2b434bd9282bbcec5e69a to your computer and use it in GitHub Desktop.
Save srph/ac8196935fb2b434bd9282bbcec5e69a to your computer and use it in GitHub Desktop.
throttle: Guarantee an async functions only resolves after X ms
import { delay } from '../time'
import { throttle } from './throttle'
// @TODO: In the future, let's write some tests. It's proving to be challenging
// because vitest has no means of flushing promises, and it causes some tests
// to fail (ergo, we expect callback to resolve faster, but it doesn't).
//
// Manual test for throttle function. To run:
// $ npm i -g ts-node && ts-node throttle.manual.ts
const tests = {
'does not resolve fast promises (1s) before specified time (3s)': async () => {
const callback = async () => {
await delay(1000)
return 'foo'
}
const start = Date.now()
const value = await throttle(callback, 3000)
const duration = Date.now() - start
console.log('value:', value)
console.log('time to resolve:', duration)
console.log('pass:', duration >= 3000)
},
'resolves slow promises (4s) immediately (3s)': async () => {
const callback = async () => {
await delay(4000)
return 'foo'
}
const start = Date.now()
const value = await throttle(callback, 3000)
const duration = Date.now() - start
console.log('value:', value)
console.log('time to resolve:', duration)
console.log('pass:', duration >= 4000)
},
'rejects errors immediately': async () => {
const callback = async () => {
return Promise.reject('foo')
}
const start = Date.now()
try {
await throttle(callback, 3000)
} catch (e) {
const duration = Date.now() - start
console.log('error:', e)
console.log('time to reject:', duration)
console.log('pass:', duration <= 1000)
}
}
}
const main = async () => {
for (const [name, testFn] of Object.entries(tests)) {
console.log('test:', name)
await testFn()
console.log('----')
}
}
main()
interface State<T> {
isPastThrottle: boolean
value: T | null
}
// This is a custom throttle function that will guarantee that the promise
// is resolved only after the specified time. Especially helpful for promises that
// resolve too fast. Use-case is to make sure a query runs at least X seconds
// so that users have time to read something from the loading state.
function throttle<T>(callback: () => Promise<T>, ms: number) {
let state: State<T> = {
isPastThrottle: false,
value: null
}
return new Promise<T>((resolve, reject) => {
callback()
.then((value) => {
state.value = value
if (state.isPastThrottle) {
resolve(state.value)
}
})
.catch(reject)
setTimeout(() => {
state.isPastThrottle = true
if (state.value) {
resolve(state.value)
}
}, ms)
})
}
export { throttle }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment