Idle Until Urgent
Idle Until Urgent

Philip Walton published a new article where he do a deep dive into improving website performance with a technique he's calling Idle Until Urgent.

The tool described in this article is actually a web performance optimization strategy called "Idle Until Urgent", implemented using JavaScript helper classes (IdleValue and IdleQueue).

Here is a summary of its principal characteristics:

1. Core Purpose and Performance Goal

The primary goal of the "Idle Until Urgent" strategy is to improve the responsiveness of a website, specifically targeting the First Input Delay (FID) metric. The strategy aims to ensure that FID remains under 100 milliseconds for 99% of page loads, as delays longer than 100ms are typically perceived as sluggish by users. The underlying problem it solves is the blocking of the browser’s main thread by long, synchronous JavaScript tasks, which prevents the browser from responding to user interactions.

2. The "Idle Until Urgent" Mechanism

This strategy is a hybrid approach positioned between eager and lazy code evaluation:

  • Idle Evaluation: The potentially expensive code is initially deferred and scheduled to run during the next available idle period using an API like requestIdleCallback.

  • Urgent Execution: If the value or the result of the task is needed immediately by another part of the program (e.g., a user interaction handler) before the idle period occurs, the scheduled idle callback is canceled, and the initialization function is run immediately and synchronously.

  • Task Management: By breaking up long synchronous code into smaller, distinct tasks (preferably less than 50ms), the strategy prevents the code from blocking user input, as the browser can prioritize input callbacks ahead of queued scheduled tasks.

3. Implementation Tools

The strategy can be implemented using specialized helper classes, available in the idlize package:

  • IdleValue: This class is designed for single, expensive property computations or values (e.g., initializing Intl.DateTimeFormat). It schedules the initialization function to run during idle time, but allows the value to be retrieved immediately via getValue() if needed urgently.

  • IdleQueue: This class manages a queue where multiple tasks (functions) can be scheduled to run when the browser has idle time. It can pause the execution of tasks to yield back to the browser if necessary.

4. Guarantees and Use Cases

The strategy addresses the reliability drawback of requestIdleCallback():

  • Ensuring Execution: For critical non-interactive tasks (like saving user state or analytics), the IdleQueue can be initialized with the option ensureTasksRun: true. This feature ensures that all pending tasks are run immediately before the page is terminated or discarded, typically by listening for the page's visibilitychange event (when visibilityState changes to hidden).

  • Applicable Scenarios: The strategy is well-suited for code that is essential but not critical to immediate user interaction. Specific examples include:

    • Analytics Code: Running initialization and sending event data idly.

    • Persisting Application State: Saving application state (e.g., Redux data) to localStorage.

    • Expensive Computations: Processing large sets of values, reading from localStorage or cookies, or calling layout APIs like getComputedStyle().

5. Performance Benefits

After implementing "Idle Until Urgent," the author demonstrated significant performance gains:

  • The longest synchronous task on the site was reduced from 233ms to 37ms.

  • The site achieved a 67% reduction in FID values at the 99th percentile (dropping from 254ms to 85ms).

  • The same amount of work is executed as before, but it is distributed over multiple, shorter tasks running during idle periods, resulting in a perfect Lighthouse score for Time to Interactive (TTI).

6. Compatibility

  • As of the article's writing, requestIdleCallback() is natively supported by Chrome and Firefox.

  • The helper classes use a fallback to setTimeout in browsers that do not natively support requestIdleCallback(). This fallback is still beneficial because browsers can prioritize user input ahead of tasks queued via setTimeout().