Skip to content

Instantly share code, notes, and snippets.

@webNeat
Created April 4, 2024 07:51
Show Gist options
  • Save webNeat/d4444931e0d2d37502815c6e7b0b6a1e to your computer and use it in GitHub Desktop.
Save webNeat/d4444931e0d2d37502815c6e7b0b6a1e to your computer and use it in GitHub Desktop.
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