requestIdleCallback 在移动端为什么不生效?

宇文俊衡 阅读 18

我在做一个移动端的长列表渲染优化,尝试用 requestIdleCallback 来分片处理数据,但在 iOS Safari 和部分安卓浏览器上完全没反应,回调根本不执行。查了下兼容性,难道真的一点办法都没有吗?

我试过加 polyfill,也用 setTimeout 降级了,但想搞清楚是不是我用法有问题。比如下面这段代码:

requestIdleCallback((deadline) => {
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    const task = tasks.shift();
    processTask(task);
  }
  if (tasks.length > 0) {
    requestIdleCallback(arguments.callee);
  }
});
我来解答 赞 7 收藏
二维码
手机扫码查看
1 条解答
IT人颖杰
你这问题我太熟悉了,去年也踩过这个坑,不是你写法有问题,是 iOS Safari 的 requestIdleCallback 实现有 bug —— 它只有在主线程真正“空闲”时才触发回调,而移动端页面通常一直在做滚动、动画、JS 执行这些事,主线程根本“空”不下来,所以回调永远不执行。

更坑的是,iOS 13 之前压根没实现这个 API,iOS 13+ 虽然有,但行为和 Chrome 差很多,经常卡住不回调,尤其在页面有滚动或动画时。

你那段代码逻辑没问题,但用 arguments.callee 是个坏习惯,而且递归调用时没防抖或节流,容易把主线程又塞满。

我现在的做法是:
- 优先用 requestIdleCallback,但加个兜底计时器
- 每次调用前先判断下浏览器是否真的支持且“活”着(比如用一个 flag 标记上次回调是否执行)
- 分片时加个最小时间片(比如至少跑 1ms),避免频繁调度

下面这样写更清晰点:

let idleId = null;
let tasks = [];

function processTasks(deadline) {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
const task = tasks.shift();
processTask(task);
}

if (tasks.length > 0) {
idleId = requestIdleCallback(processTasks);
} else {
idleId = null;
}
}

function scheduleTasks(newTasks) {
tasks = tasks.concat(newTasks);
if (!idleId) {
idleId = requestIdleCallback(processTasks);
}
}

// 兜底方案:如果 100ms 内没触发,强制用 setTimeout
let fallbackTimer = setTimeout(() => {
if (tasks.length > 0 && !idleId) {
processTasks({ timeRemaining: () => 4 }); // 假装还有 4ms
}
}, 100);


另外提醒下,别用 arguments.callee,ES5 严格模式下直接报错,而且递归名明确点更利于调试。

如果真要兼容 iOS 12 以下,建议直接上 setTimeout + performance.now() 自己模拟 idle 行为,虽然丑点但稳。
点赞 4
2026-02-26 15:03