You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 17:45:29