JS中CLS测量始终返回0的异步问题及Promise实现求助
解决CLS始终返回0的异步问题
你猜的没错,CLS返回0是因为代码同步执行时,CLS的最终值还没被PerformanceObserver计算出来。和LCP/FCP/FID不同,CLS是动态累积的,会在页面整个生命周期中更新,直到用户离开页面或5秒内没有新的布局偏移时才会确定最终值。而你的代码在脚本执行完就立刻输出clsValue,此时它还是初始的0。
解决方案
我们可以用Promise包装每个性能指标的获取逻辑,再通过async/await等待所有指标(尤其是CLS)就绪后再统一输出。对于CLS,需要监听页面卸载事件(beforeunload)来获取最终值,或者设置合理的超时时间,确保拿到完整的累积结果。
重构后的代码
// 封装获取FCP的Promise function getFCP() { return new Promise(resolve => { if ('PerformanceObserver' in window) { const observer = new PerformanceObserver(entryList => { const entries = entryList.getEntriesByName('first-contentful-paint'); if (entries.length) { observer.disconnect(); resolve(entries[0].startTime); } }); observer.observe({ type: 'paint', buffered: true }); } else { resolve(null); } }); } // 封装获取LCP的Promise function getLCP() { return new Promise(resolve => { if ('PerformanceObserver' in window) { const observer = new PerformanceObserver(entryList => { const entries = entryList.getEntries(); if (entries.length) { observer.disconnect(); resolve(entries[0].startTime); } }); observer.observe({ type: 'largest-contentful-paint', buffered: true }); } else { resolve(null); } }); } // 封装获取FID的Promise function getFID() { return new Promise(resolve => { if ('PerformanceObserver' in window) { const observer = new PerformanceObserver(entryList => { const entries = entryList.getEntries(); if (entries.length) { observer.disconnect(); const fid = entries[0].processingStart - entries[0].startTime; resolve(fid); } }); observer.observe({ type: 'first-input', buffered: true }); } else { resolve(null); } }); } // 封装获取CLS的Promise(监听直到页面卸载或超时) function getCLS() { return new Promise(resolve => { let clsValue = 0; let sessionValue = 0; let sessionEntries = []; if ('PerformanceObserver' in window) { const observer = new PerformanceObserver(entryList => { for (const entry of entryList.getEntries()) { if (!entry.hadRecentInput) { const firstSessionEntry = sessionEntries[0]; const lastSessionEntry = sessionEntries[sessionEntries.length - 1]; if (sessionValue && entry.startTime - lastSessionEntry.startTime < 1000 && entry.startTime - firstSessionEntry.startTime < 5000) { sessionValue += entry.value; sessionEntries.push(entry); } else { sessionValue = entry.value; sessionEntries = [entry]; } if (sessionValue > clsValue) { clsValue = sessionValue; } } } }); observer.observe({ type: 'layout-shift', buffered: true }); // 页面卸载时返回最终CLS值 window.addEventListener('beforeunload', () => { observer.disconnect(); resolve(clsValue); }); // 超时处理(页面加载后10秒自动返回当前值) setTimeout(() => { observer.disconnect(); resolve(clsValue); }, 10000); } else { resolve(null); } }); } // 获取同步性能指标 function getSyncMetrics() { const time = window.performance.timing; if (!time) return {}; return { dns: time.domainLookupEnd - time.domainLookupStart, tcp: time.connectEnd - time.connectStart, ssl: time.requestStart - time.secureConnectionStart, firstByte: time.responseStart - time.navigationStart, onload: time.loadEventEnd - time.loadEventStart, request: time.responseStart - time.requestStart, load: time.loadEventEnd - time.loadEventStart, redirect: time.redirectEnd - time.redirectStart, basepage: time.responseEnd - time.navigationStart }; } // 主函数:等待所有指标就绪后输出 async function collectPerformanceMetrics() { // 获取同步指标 const syncMetrics = getSyncMetrics(); // 异步获取所有指标 const [fcp, lcp, fid, cls] = await Promise.all([ getFCP(), getLCP(), getFID(), getCLS() ]); // 统一输出所有指标 console.log('同步性能指标:', syncMetrics); console.log('FCP:', fcp); console.log('LCP:', lcp); console.log('FID:', fid); console.log('CLS:', cls); // 可将指标赋值给变量或发送到服务器 return { ...syncMetrics, fcp, lcp, fid, cls }; } // 执行收集函数 collectPerformanceMetrics();
代码关键点说明
- 每个异步性能指标都被包装成Promise,确保能等待结果返回后再执行后续逻辑。
- CLS的处理增加了
beforeunload监听,保证页面卸载时能拿到最终的累积值;同时设置10秒超时,避免在无交互页面一直等待。 - 用
Promise.all并行等待所有异步指标,最后统一输出,确保所有值都是最新的。 - 同步指标单独提取,不影响异步逻辑的执行。
内容的提问来源于stack exchange,提问作者Caelaran
相关产品推荐
相关产品推荐

