Created
April 4, 2024 07:51
-
-
Save webNeat/d4444931e0d2d37502815c6e7b0b6a1e to your computer and use it in GitHub Desktop.
This file contains hidden or 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
const noop = () => { | |
/* do nothing */ | |
}; | |
// ---------- old version ---------- | |
export function asyncThrottle(func, { interval = 1000, onError = noop } = {}) { | |
if (typeof func !== "function") throw new Error("argument is not function."); | |
let running = false; | |
let lastTime = 0; | |
let timeout; | |
let currentArgs = null; | |
const execFunc = async () => { | |
if (currentArgs) { | |
const args = currentArgs; | |
currentArgs = null; | |
try { | |
running = true; | |
await func(...args); | |
} catch (error) { | |
onError(error); | |
} finally { | |
lastTime = Date.now(); // this line must after 'func' executed to avoid two 'func' running in concurrent. | |
running = false; | |
} | |
} | |
}; | |
const delayFunc = async () => { | |
clearTimeout(timeout); | |
timeout = setTimeout(() => { | |
if (running) { | |
delayFunc(); // Will come here when 'func' execution time is greater than the interval. | |
} else { | |
execFunc(); | |
} | |
}, interval); | |
}; | |
return (...args) => { | |
currentArgs = args; | |
const tooSoon = Date.now() - lastTime < interval; | |
if (running || tooSoon) { | |
delayFunc(); | |
} else { | |
execFunc(); | |
} | |
}; | |
} | |
// ---------- new version ---------- | |
export function newAsyncThrottle( | |
func, | |
{ interval = 1000, onError = noop } = {} | |
) { | |
if (typeof func !== "function") throw new Error("argument is not function."); | |
let nextExecutionTime = 0; | |
let lastArgs = null; | |
let isExecuting = false; | |
let isScheduled = false; | |
return async (...args) => { | |
lastArgs = args; | |
if (isScheduled) return; | |
isScheduled = true; | |
while (isExecuting) { | |
await new Promise((done) => setTimeout(done, interval)); | |
} | |
while (Date.now() < nextExecutionTime) { | |
await new Promise((done) => | |
setTimeout(done, nextExecutionTime - Date.now()) | |
); | |
} | |
isScheduled = false; | |
isExecuting = true; | |
try { | |
await func(...lastArgs); | |
} catch (error) { | |
try { | |
onError(error); | |
} catch {} | |
} | |
nextExecutionTime = Date.now() + interval; | |
isExecuting = false; | |
}; | |
} | |
//--------------- measure function --------------- | |
// Throttles a simple function and runs it 1_000_000 times is sequence. | |
// This is not perfect, but it gives an idea of the performance. | |
export async function measure(trottle) { | |
const fn = trottle(async (a, b) => a + b, { | |
interval: 500, | |
}); | |
const start = performance.now(); | |
for (let i = 0; i < 1_000_000; i++) { | |
fn(i, i); | |
} | |
return Math.floor(performance.now() - start); | |
} | |
function average(numbers) { | |
return Math.floor(numbers.reduce((a, b) => a + b, 0) / numbers.length); | |
} | |
//--------------- main function --------------- | |
async function main() { | |
const measures = { | |
old: [], | |
new: [], | |
}; | |
let repetitions = 32; | |
for (let i = 0; i < repetitions; i++) { | |
measures.old.push(await measure(asyncThrottle)); | |
measures.new.push(await measure(newAsyncThrottle)); | |
} | |
console.log(`The avg time it takes in milliseconds to run 1_000_000 times`); | |
console.log("old version:", average(measures.old)); | |
console.log("new version:", average(measures.new)); | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment