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. 注意事项

  1. 不要用于关键任务:空闲回调的执行时间不确定
  2. 避免修改DOM:可能引起重排重绘,影响性能
  3. 任务要可中断:确保能在timeRemaining()用尽时暂停
  4. 内存管理:及时取消不再需要的回调
  5. 超时设置谨慎:超时可能影响用户体验
// 不好的做法 - 可能阻塞用户交互
requestIdleCallback(() => {
    // 执行同步的繁重计算
    heavySynchronousCalculation(); // ❌
});

// 好的做法 - 任务可分割
requestIdleCallback(function processChunk(deadline) {
    while (deadline.timeRemaining() > 0 && hasMoreWork) {
        processWorkUnit();
    }
    
    if (hasMoreWork) {
        requestIdleCallback(processChunk);
    }
});

requestIdleCallback 是优化网页性能的重要工具,特别适合处理那些重要但不紧急的后台任务。

posted @ 2025-10-14 11:52  阿木隆1237  阅读(73)  评论(0)    收藏  举报