Featured image of post 使用 requestIdleCallback 执行非阻塞脚本Featured image of post 使用 requestIdleCallback 执行非阻塞脚本

使用 requestIdleCallback 执行非阻塞脚本

了解如何使用 requestIdleCallback API 在浏览器空闲期间运行低优先级后台任务,从而保留交互帧速率。

介绍

在构建复杂的 Web 应用程序时,保持流畅的动画和响应式用户输入(理想情况下匹配 60+ FPS 帧速率)对于良好的用户体验至关重要。

然而,现代应用程序通常需要执行低优先级的后台操作,例如发送分析数据、同步缓存、解析遥测日志或预取资产。

作为标准异步承诺或立即计时器执行这些非紧急任务可能会阻塞主线程中帧,导致界面卡顿或增加下次绘制交互 (INP) 分数。

requestIdleCallback API 解决了这个问题。本文回顾了 API 的工作原理并分享了生产代码库的实际用例。


1.什么是requestIdleCallback?

requestIdleCallback 是一个本机浏览器 API,允许您对低优先级后台任务进行排队,以便在浏览器空闲期间(主线程完成渲染更新并等待用户输入时的间隙)运行。

浏览器渲染周期

通常,浏览器会尝试在单帧(大约 16.6 毫秒)内执行以下步骤:

[Single Frame (approx 16.6ms)]
 ├─► 1. Process user inputs (clicks, keypresses)
 ├─► 2. Execute active macro-tasks (setTimeout/setInterval)
 ├─► 3. Run requestAnimationFrame updates
 ├─► 4. Recalculate layout & paint pixels
 └─► 5. 【Browser Idle Period】 ◄── requestIdleCallback runs here!

如果浏览器提前完成渲染更新,它将使用剩余时间来执行 requestIdleCallback 中排队的任务。如果发生高优先级事件(例如单击或触摸输入),浏览器会立即将控制权交还给主渲染循环,以保持 UI 响应。


2. 基本设置和语法

标准实施

// A low-priority task queue processor
function sendAnalyticsData(deadline) {
  // deadline.timeRemaining() returns the milliseconds left in the current idle frame
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    const task = tasks.shift();
    executeTask(task);
  }

  // If tasks remain, queue them for the next idle period
  if (tasks.length > 0) {
    requestIdleCallback(sendAnalyticsData);
  }
}

// Queue the callback
requestIdleCallback(sendAnalyticsData);

实施超时选项

如果浏览器始终处于忙碌状态,则在 requestIdleCallback 中排队的任务可能会无限期延迟。

为了防止这种情况,您可以设置 timeout 选项。这告诉浏览器在指定的截止日期之前执行回调,即使它必须在繁忙的帧期间运行。

// Schedule execution, but force it to run if 2 seconds (2000ms) pass
requestIdleCallback(sendAnalyticsData, { timeout: 2000 });

3. 实际用例

requestIdleCallback 对于不会立即更改活动 DOM 结构的非可视任务很有用:

① 批量遥测和分析发送

不要立即为每个用户点击触发 HTTP 请求,而是将遥测点存储在本地数组中,并在浏览器空闲时批量发送它们。

② 同步客户端存储

使用它来写入日志、清理过期的索引键或将客户端数据同步到 localStorageIndexedDB ,而不会阻止用户输入。

③ 预取资产和组件

在空闲期间预加载大量代码或下游图像,以便当用户导航到新页面时它们已准备好。


重要警告:避免 DOM 操作

切勿修改 requestIdleCallback 函数内的 DOM。

更新 DOM 会强制浏览器重新计算页面布局(回流)。这会使活动空闲状态无效,并可能导致布局抖动。如果您需要根据空闲任务进行视觉更改,请将 DOM 更新传递给 requestAnimationFrame

requestIdleCallback((deadline) => {
  const result = computeData(deadline);

  // Schedule DOM writes on the animation thread
  requestAnimationFrame(() => {
    element.textContent = result;
  });
});

结论

使用 requestIdleCallback 通过将非紧急任务转移到浏览器空闲时间来帮助您优化页面性能。

  1. 使用 requestIdleCallback 将非紧急后台任务推迟到空闲帧。
  2. 使用 timeout 参数可以防止任务无限期延迟。
  3. 避免直接在空闲回调中运行 DOM 写入。

将此 API 合并到您的应用程序中,让您的 UI 感觉能够响应用户输入。