RequestldleCallback
requestIdleCallback
requestIdleCallback 是一个浏览器API,允许开发者在浏览器空闲时期执行后台或低优先级的任务,而不会影响关键的用户交互和动画性能。
1. 基本概念
工作原理
// 基本语法
const handle = requestIdleCallback(callback[, options])
// 取消回调
cancelIdleCallback(handle)
基本使用
// 简单的空闲回调
requestIdleCallback(function(deadline) {
// deadline 对象包含:
// - didTimeout: boolean - 回调是否因超时而被执行
// - timeRemaining(): function - 返回当前帧剩余的时间(毫秒)
while (deadline.timeRemaining() > 0) {
// 执行一些低优先级任务
performLowPriorityWork();
}
// 如果还有未完成的工作,可以再次请求
if (hasMoreWork) {
requestIdleCallback(arguments.callee);
}
});
2. 详细用法
deadline 对象详解
requestIdleCallback(function(deadline) {
console.log('是否超时:', deadline.didTimeout);
console.log('剩余时间:', deadline.timeRemaining(), 'ms');
// timeRemaining() 最大返回50ms,确保不阻塞用户交互
const timeRemaining = deadline.timeRemaining();
if (timeRemaining > 0) {
// 有时间可以执行任务
processBatchOfItems();
}
});
带选项的调用
// 设置超时时间(毫秒)
requestIdleCallback(
function(deadline) {
if (deadline.didTimeout) {
// 因超时触发,需要尽快完成工作
processWorkQuickly();
} else {
// 正常空闲时期,可以按部就班工作
processWorkNormally(deadline);
}
},
{ timeout: 2000 } // 2秒后即使不空闲也执行
);
3. 实际应用场景
场景1:数据预处理
class DataPreprocessor {
constructor() {
this.pendingWork = [];
this.isProcessing = false;
}
schedulePreprocessing(dataChunks) {
this.pendingWork.push(...dataChunks);
if (!this.isProcessing) {
this.startProcessing();
}
}
startProcessing() {
this.isProcessing = true;
requestIdleCallback((deadline) => {
this.processBatch(deadline);
});
}
processBatch(deadline) {
while (this.pendingWork.length > 0 && deadline.timeRemaining() > 0) {
const chunk = this.pendingWork.shift();
this.preprocessChunk(chunk);
}
if (this.pendingWork.length > 0) {
// 还有工作,继续调度
requestIdleCallback((deadline) => {
this.processBatch(deadline);
});
} else {
this.isProcessing = false;
}
}
preprocessChunk(chunk) {
// 模拟数据预处理
console.log('处理数据块:', chunk);
// 实际可能是:数据清洗、格式转换、计算等
}
}
// 使用示例
const preprocessor = new DataPreprocessor();
preprocessor.schedulePreprocessing([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
场景2:日志批量发送
class LazyLogger {
constructor() {
this.logQueue = [];
this.isScheduled = false;
}
log(message) {
this.logQueue.push({
message,
timestamp: Date.now()
});
this.scheduleFlush();
}
scheduleFlush() {
if (this.isScheduled || this.logQueue.length === 0) {
return;
}
this.isScheduled = true;
requestIdleCallback(
(deadline) => {
this.flushLogs(deadline);
},
{ timeout: 5000 } // 最多等待5秒
);
}
flushLogs(deadline) {
const batch = [];
while (this.logQueue.length > 0 &&
(deadline.timeRemaining() > 0 || deadline.didTimeout)) {
batch.push(this.logQueue.shift());
// 限制单次批量大小
if (batch.length >= 50) break;
}
if (batch.length > 0) {
this.sendToServer(batch);
}
if (this.logQueue.length > 0) {
this.scheduleFlush();
} else {
this.isScheduled = false;
}
}
sendToServer(logs) {
// 模拟发送到服务器
console.log('发送日志到服务器:', logs);
// fetch('/api/logs', { method: 'POST', body: JSON.stringify(logs) })
}
}
// 使用示例
const logger = new LazyLogger();
logger.log('用户登录');
logger.log('页面浏览');
logger.log('按钮点击');
场景3:DOM更新优化
class LazyDOMUpdater {
constructor(container) {
this.container = container;
this.pendingUpdates = [];
this.isUpdating = false;
}
scheduleUpdate(element, newContent) {
this.pendingUpdates.push({ element, newContent });
if (!this.isUpdating) {
this.startUpdates();
}
}
startUpdates() {
this.isUpdating = true;
requestIdleCallback((deadline) => {
this.processUpdates(deadline);
});
}
processUpdates(deadline) {
const fragment = document.createDocumentFragment();
let updatesProcessed = 0;
while (this.pendingUpdates.length > 0 && deadline.timeRemaining() > 0) {
const update = this.pendingUpdates.shift();
this.applyUpdate(fragment, update);
updatesProcessed++;
// 限制单批次更新数量
if (updatesProcessed >= 10) break;
}
// 一次性更新DOM
this.container.appendChild(fragment);
if (this.pendingUpdates.length > 0) {
requestIdleCallback((deadline) => {
this.processUpdates(deadline);
});
} else {
this.isUpdating = false;
}
}
applyUpdate(fragment, update) {
const div = document.createElement('div');
div.textContent = update.newContent;
fragment.appendChild(div);
}
}
// 使用示例
const container = document.getElementById('content');
const updater = new LazyDOMUpdater(container);
// 批量调度更新
for (let i = 0; i < 100; i++) {
updater.scheduleUpdate(null, `内容项 ${i}`);
}
4. 高级用法和最佳实践
结合 Promise 使用
function idlePromise(timeout) {
return new Promise((resolve) => {
requestIdleCallback(
(deadline) => {
resolve(deadline);
},
timeout ? { timeout } : undefined
);
});
}
// 使用 async/await
async function processHeavyWork() {
const deadline = await idlePromise(1000);
if (deadline.timeRemaining() > 0 || deadline.didTimeout) {
// 执行繁重任务
await heavyCalculation();
}
}
性能监控
function monitoredIdleCallback(callback, options) {
const startTime = performance.now();
return requestIdleCallback((deadline) => {
const callbackStart = performance.now();
const queueTime = callbackStart - startTime;
callback(deadline);
const executionTime = performance.now() - callbackStart;
// 监控指标
console.log(`队列等待时间: ${queueTime}ms`);
console.log(`执行时间: ${executionTime}ms`);
console.log(`剩余时间: ${deadline.timeRemaining()}ms`);
// 可以发送到监控系统
if (executionTime > 50) {
console.warn('空闲回调执行时间过长');
}
}, options);
}
错误处理
function safeIdleCallback(callback, options) {
return requestIdleCallback((deadline) => {
try {
callback(deadline);
} catch (error) {
console.error('空闲回调执行出错:', error);
// 错误上报
reportError(error);
}
}, options);
}
// 使用安全版本
safeIdleCallback((deadline) => {
// 可能出错的代码
riskyOperation();
});
5. 兼容性和回退方案
// 兼容性检查
const supportsIdleCallback = 'requestIdleCallback' in window;
// 回退到 setTimeout
const idleCallback = supportsIdleCallback ?
requestIdleCallback :
(callback) => setTimeout(() => callback({
timeRemaining: () => 50, // 模拟50ms
didTimeout: false
}), 1);
const cancelIdleCallback = supportsIdleCallback ?
cancelIdleCallback :
clearTimeout;
// 使用兼容版本
const handle = idleCallback((deadline) => {
// 你的代码
});
// 需要时取消
// cancelIdleCallback(handle);
6. 注意事项
- 不要用于关键任务:空闲回调的执行时间不确定
- 避免修改DOM:可能引起重排重绘,影响性能
- 任务要可中断:确保能在timeRemaining()用尽时暂停
- 内存管理:及时取消不再需要的回调
- 超时设置谨慎:超时可能影响用户体验
// 不好的做法 - 可能阻塞用户交互
requestIdleCallback(() => {
// 执行同步的繁重计算
heavySynchronousCalculation(); // ❌
});
// 好的做法 - 任务可分割
requestIdleCallback(function processChunk(deadline) {
while (deadline.timeRemaining() > 0 && hasMoreWork) {
processWorkUnit();
}
if (hasMoreWork) {
requestIdleCallback(processChunk);
}
});
requestIdleCallback 是优化网页性能的重要工具,特别适合处理那些重要但不紧急的后台任务。
挣钱养家

浙公网安备 33010602011771号