import { ONE_SECOND } from './utils/timeUtils' import { requestIdleCallback } from './requestIdleCallback' /** * Maximum delay before starting to execute tasks in the queue. We don't want to wait too long * before running tasks, as it might hurt reliability (ex: if the user navigates away, we might lose * the opportunity to send some data). We also don't want to run tasks too often, as it might hurt * performance. */ const IDLE_CALLBACK_TIMEOUT = ONE_SECOND /** * Maximum amount of time allocated to running tasks when a timeout (`IDLE_CALLBACK_TIMEOUT`) is * reached. We should not run tasks for too long as it will hurt performance, but we should still * run some tasks to avoid postponing them forever. * * Rational: Running tasks for 30ms every second (IDLE_CALLBACK_TIMEOUT) should be acceptable. */ export const MAX_EXECUTION_TIME_ON_TIMEOUT = 30 export interface TaskQueue { push(task: Task): void stop(): void } type Task = () => void export function createTaskQueue(): TaskQueue { const pendingTasks: Task[] = [] function run(deadline: IdleDeadline) { let executionTimeRemaining: () => number if (deadline.didTimeout) { const start = performance.now() executionTimeRemaining = () => MAX_EXECUTION_TIME_ON_TIMEOUT - (performance.now() - start) } else { executionTimeRemaining = deadline.timeRemaining.bind(deadline) } while (executionTimeRemaining() > 0 && pendingTasks.length) { pendingTasks.shift()!() } if (pendingTasks.length) { scheduleNextRun() } } function scheduleNextRun() { requestIdleCallback(run, { timeout: IDLE_CALLBACK_TIMEOUT }) } return { push(task) { if (pendingTasks.push(task) === 1) { scheduleNextRun() } }, stop() { pendingTasks.length = 0 }, } }