最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如何用 requestIdleCallback 在渲染间隙处理非核心埋点日志发送
时间:2026-06-28 09:59:51 编辑:袖梨 来源:一聚教程网
requestIdleCallback 不可靠,仅适用于非关键日志补发,必须搭配 timeout 保底、批量合并、卸载前强制 flush(用 pagehide + keepalive)及停留超3秒主动发送。
requestIdleCallback 什么时候真正可用
它不是 Promise,也不保证一定执行——浏览器忙的时候可能一直不调用,比如页面正在重排重绘、JS 正在执行长任务,requestIdleCallback 就会被跳过。Chrome 从 120 版本起还默认禁用了该 API(需手动开启实验标志),而 Safari 和 Firefox 早已移除支持。所以别把它当「可靠定时器」用,只适合做「能发就发、不发也无妨」的日志补发。
埋点日志必须带 timeout 保底机制
靠 requestIdleCallback 单独发日志,大概率会漏发:用户切页、刷新、关标签前 idle 时间根本来不及触发。必须搭配 setTimeout 或页面卸载钩子兜底:
function sendLogWithIdle(log) { const timeoutId = setTimeout(() => { sendLogNow(log); // 立即发,不等 idle }, 2000); requestIdleCallback(() => { clearTimeout(timeoutId); sendLogNow(log); }, { timeout: 2000 });}
-
timeout: 2000是关键参数,否则即使设了 timeout,回调也可能永远不进 -
clearTimeout要放在 callback 里,避免 idle 触发后又走 timeout 分支重复发送 - 注意:Firefox/Safari 不支持
timeout选项,得靠外层setTimeout模拟
批量合并日志再发,别每个埋点都 call 一次
频繁调用 requestIdleCallback 会堆积大量微任务,反而增加调度开销。应该把日志暂存,每 500ms 合并一次,再用单次 idle 回调发出:
let pendingLogs = [];let idleHandle = null;function queueLog(log) { pendingLogs.push(log); if (!idleHandle) { idleHandle = requestIdleCallback(flushLogs, { timeout: 1000 }); }}function flushLogs({ didTimeout }) { if (pendingLogs.length === 0) return; if (didTimeout || pendingLogs.length >= 10) { sendBatch(pendingLogs); pendingLogs = []; } else { // 还没到量,再等一会儿 idleHandle = requestIdleCallback(flushLogs, { timeout: 500 }); }}
-
didTimeout表示浏览器实在腾不出空,此时应立即发,避免积压 - 限制单次最多 10 条,防止单次发送过大阻塞主线程
- 不要在
flushLogs里直接递归调用requestIdleCallback,容易栈溢出;用变量控制是否已挂起
卸载前强制 flush,但别用 beforeunload
beforeunload 事件里禁止发起网络请求(Chrome 会静默丢弃),正确做法是监听 visibilitychange + pagehide:
- 当
document.visibilityState === 'hidden',立刻 flush 日志 -
pagehide事件比unload更早触发,且允许 fetch 发送(需加keepalive: true) -
fetch(url, { method: 'POST', body: JSON.stringify(logs), keepalive: true })是唯一能在页面销毁前发出的可靠方式
复杂点在于:不同浏览器对 keepalive 的支持程度不一,Safari 仍可能截断;所以最保险的是,只要用户停留超过 3 秒,就主动 flush 一次,不全指望卸载时补救。