Last active
August 28, 2024 08:27
-
-
Save srph/ac8196935fb2b434bd9282bbcec5e69a to your computer and use it in GitHub Desktop.
throttle: Guarantee an async functions only resolves after X ms
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
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() |
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
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