requestIdleCallback()

requestIdleCallback(callback, { timeout }) runs low-priority work such as analytics or prefetching when the browser is idle (basics in Working on Idle Time). The deadline's timeRemaining() never exceeds 50 ms, and didTimeout is true when timeout forced the call. It is Limited availability: Chrome 47 1 + and Firefox 55 555 +, but Safari 10 only in preview builds.

Draining a work queue in idle periods, with a timer fallbackJavaScript
const onIdle = window.requestIdleCallback ??
  ((cb) => setTimeout(() => cb({ didTimeout: false, timeRemaining: () => 10 }), 1));
const queue = Array.from({ length: 4000 }, (_, i) => i);
let periods = 0;
onIdle(function drain(deadline) {
  periods++;
  while (queue.length && (deadline.timeRemaining() > 1 || deadline.didTimeout)) {
    for (let i = 0, n = queue.shift(); i < 2e5; i++) Math.sqrt(i + n);   // ~0.1 ms each
  }
  if (queue.length) onIdle(drain, { timeout: 2000 });
  else console.log(`4000 items in ${periods} idle periods`);
}, { timeout: 2000 });
Output
4000 items in 8 idle periods

Make DOM changes in requestAnimationFrame(), not here.