自定义性能上报数据怎么避免重复发送?

博主育柯 阅读 12

我在做前端性能监控,用 PerformanceObserver 收集了首屏时间,但发现每次页面刷新都会重复上报相同的数据。

试过加 localStorage 标记,但用户清缓存就失效了。有没有更靠谱的方式确保每条记录只上报一次?

现在上报逻辑是这样的:

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.name === 'first-contentful-paint') {
      fetch('/report', {
        method: 'POST',
        body: JSON.stringify({ fcp: entry.startTime })
      });
    }
  }
});
observer.observe({ entryTypes: ['paint'] });
我来解答 赞 2 收藏
二维码
手机扫码查看
1 条解答
UX焕焕
UX焕焕 Lv1
解决这个问题,我们可以考虑使用浏览器的 IndexedDB 来存储已经上报过的数据。IndexedDB 是一个事务型的数据库系统,它允许你在浏览器中存储大量的结构化数据,并且可以通过索引高效地检索这些数据。相比 localStorage,IndexedDB 更适合存储复杂的数据结构,并且不容易被用户误删。

下面是详细的步骤:

1. 创建 IndexedDB 数据库:我们需要在应用启动时检查是否存在相应的数据库和对象存储空间,如果不存在则创建它们。

2. 保存已上报的数据:每次成功上报数据后,我们将这条数据的唯一标识(比如 FCP 时间戳)保存到 IndexedDB 中。

3. 检查数据是否已上报:在准备上报数据之前,先从 IndexedDB 中查询这条数据的标识是否存在。如果存在,则跳过上报;否则执行上报操作。

下面是具体的代码实现:

// 打开或创建 IndexedDB 数据库
const dbPromise = indexedDB.open('performanceDB', 1);

dbPromise.onupgradeneeded = function(event) {
// 创建对象存储空间
const db = event.target.result;
const objectStore = db.createObjectStore('reports', { keyPath: 'id', autoIncrement: true });
objectStore.createIndex('fcp', 'fcp', { unique: true });
};

dbPromise.onerror = function(event) {
console.error('Database error:', event);
};

const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
checkAndReport(entry.startTime);
}
}
});

observer.observe({ entryTypes: ['paint'] });

function checkAndReport(fcpTime) {
dbPromise.onsuccess = function(event) {
const db = event.target.result;
const transaction = db.transaction(['reports'], 'readonly');
const objectStore = transaction.objectStore('reports');
const index = objectStore.index('fcp');

// 查询是否已存在该 FCP 时间戳
const request = index.get(fcpTime);

request.onsuccess = function(event) {
if (event.target.result) {
console.log('This FCP time has already been reported.');
} else {
reportFCP(fcpTime);
}
};

request.onerror = function(event) {
console.error('Error checking FCP time:', event);
};
};
}

function reportFCP(fcpTime) {
fetch('/report', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ fcp: fcpTime })
}).then(response => {
if (response.ok) {
saveReportedFCP(fcpTime);
} else {
console.error('Failed to report FCP time.');
}
}).catch(error => {
console.error('Network error:', error);
});
}

function saveReportedFCP(fcpTime) {
dbPromise.onsuccess = function(event) {
const db = event.target.result;
const transaction = db.transaction(['reports'], 'readwrite');
const objectStore = transaction.objectStore('reports');

// 将 FCP 时间戳保存到数据库中
const request = objectStore.add({ fcp: fcpTime });

request.onsuccess = function() {
console.log('FCP time saved successfully.');
};

request.onerror = function(event) {
console.error('Error saving FCP time:', event);
};
};
}


这里需要注意的是,IndexedDB 的操作是异步的,所以我们需要处理各种事件回调。通过这种方式,我们可以在浏览器端可靠地记录已经上报的数据,避免重复发送相同的性能指标。
点赞
2026-03-20 22:11