Write batchCalls(fn, { wait, maxSize }). It returns a function, add(item), that collects items and passes them to fn in batches, as one array, instead of one at a time. For example, analytics events can reach the server in one request instead of dozens.
The first item added starts a batch and a wait ms timer. Items added later join the batch without restarting the timer. When the timer fires, fn is called once with an array of the batch's items, in the order they were added, and the next item starts a new batch.
When a batch reaches maxSize items, fn gets it right away, during the call that added the last item, and the batch's timer is cancelled. maxSize is optional: without it, only the timer ends a batch.
add also has a flush() method that passes the current batch to fn right away, for example before the page closes. The next item starts a new batch.
fn is never called with an empty array, so flush() with nothing collected does nothing. Each call of fn gets an array of its own, which never changes after fn gets it.
const track = batchCalls((events) => console.log(events), { wait: 1000, maxSize: 3 });
track("view"); // at 0 ms: starts a batch
track("click"); // at 400 ms: joins it
// at 1000 ms: logs ["view", "click"]
track("a");
track("b");
track("c"); // the batch is full: logs ["a", "b", "c"] right away
track("d");
track.flush(); // logs ["d"]
track.flush(); // nothing collected: does nothing
const clock = useFakeTimers();
const fn = spy();
const add = batchCalls(fn, { wait: 100, maxSize: 10 });
add("a");
clock.tick(60);
add("b");
add("c");
clock.tick(39);
expect(fn).toHaveBeenCalledTimes(0);
clock.tick(1);
expect(fn).toHaveBeenCalledTimes(1);
expect(fn).toHaveBeenLastCalledWith(["a", "b", "c"]);
const clock = useFakeTimers();
const fn = spy();
const add = batchCalls(fn, { wait: 100 });
add(1);
clock.tick(650);
add(2);
add(3);
clock.tick(99);
expect(fn.calls.map((call) => call.args[0])).toEqual([[1]]);
clock.tick(1);
expect(fn.calls.map((call) => call.args[0])).toEqual([[1], [2, 3]]);
const clock = useFakeTimers();
const fn = spy();
const add = batchCalls(fn, { wait: 100, maxSize: 3 });
add(1);
add(2);
expect(fn).toHaveBeenCalledTimes(0);
add(3);
expect(fn).toHaveBeenCalledTimes(1);
expect(fn).toHaveBeenLastCalledWith([1, 2, 3]);
add(4);
clock.tick(100);
expect(fn.calls.map((call) => call.args[0])).toEqual([[1, 2, 3], [4]]);
const clock = useFakeTimers();
const fn = spy();
const add = batchCalls(fn, { wait: 100, maxSize: 2 });
add(1);
clock.tick(50);
add(2); // full: fn gets [1, 2] now
clock.tick(10);
add(3); // a new batch, due at 160 ms
clock.tick(99);
expect(fn.calls.map((call) => call.args[0])).toEqual([[1, 2]]);
clock.tick(1);
expect(fn.calls.map((call) => call.args[0])).toEqual([[1, 2], [3]]);
const clock = useFakeTimers();
const fn = spy();
const add = batchCalls(fn, { wait: 100, maxSize: 10 });
add("a");
add("b");
clock.tick(50);
add.flush();
expect(fn).toHaveBeenCalledTimes(1);
expect(fn).toHaveBeenLastCalledWith(["a", "b"]);
add("c"); // a new batch, due at 150 ms
clock.tick(99);
expect(fn).toHaveBeenCalledTimes(1);
clock.tick(1);
expect(fn).toHaveBeenCalledTimes(2);
expect(fn).toHaveBeenLastCalledWith(["c"]);
const clock = useFakeTimers();
const fn = spy();
const add = batchCalls(fn, { wait: 100, maxSize: 2 });
add.flush();
add(1);
add(2);
clock.tick(1000);
add.flush();
expect(fn.calls.map((call) => call.args[0])).toEqual([[1, 2]]);
const clock = useFakeTimers();
const batches = [];
const add = batchCalls((items) => batches.push(items), { wait: 100, maxSize: 2 });
add(1);
add(2);
add(3);
clock.tick(100);
expect(batches).toEqual([[1, 2], [3]]);
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Batch calls. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.