编程 前端必学技巧:用户离开页面时如何可靠地发送 HTTP 请求?

2025-08-16 09:03:44 +0800 CST views 18

🚀 前端必学技巧:用户离开页面时如何可靠地发送 HTTP 请求?

在 Web 应用开发中,我们经常需要在用户离开页面时上报一些数据,例如:

  • 记录点击日志
  • 上报性能指标
  • 发送埋点数据

但是,这里有一个大坑:页面跳转时,HTTP 请求很可能还没发出去就被浏览器取消。如果后端依赖这些日志数据进行分析,那么部分数据就会丢失。

本文将带你从常见方案 → 缺陷分析 → 现代浏览器提供的终极解决方案,完整搞懂这个问题。


❌ 1. 直接使用 fetch:请求被取消

一种常见写法是给跳转的链接绑定 click 事件,先发送日志请求,再执行跳转:

document.getElementById('link').addEventListener('click', (e) => {
  e.preventDefault();

  fetch("/log", {
    method: "POST",
    headers: { "Content-Type": "application/json" }, 
    body: JSON.stringify({ name: 'FedJavaScript' }),
  });

  window.location = e.target.href;
});

问题来了:

  • fetch 是异步的,浏览器不会等待它完成
  • 页面一旦跳转,未完成的请求直接被取消
  • 数据可能根本没到服务器

⏳ 2. await fetch:用户卡住了

那我们是不是可以等待请求完成后,再执行跳转?

document.getElementById('link').addEventListener('click', async (e) => {
  e.preventDefault();

  await fetch("/log", {
    method: "POST",
    headers: { "Content-Type": "application/json" }, 
    body: JSON.stringify({ name: 'FedJavaScript' }),
  });

  window.location = e.target.href;
});

这样确实能保证请求发出去,但有两个问题:

  • 移动端 300ms 延迟已经能感知,更别说等一个慢请求了
  • 如果 /log 接口返回过慢,用户会觉得页面「卡住了」

显然,这不是好体验。


✅ 3. 现代解决方案:keepalive

好在现代浏览器(Chrome、Safari、Firefox、Edge 等)都支持了 fetchkeepalive 参数。

这个参数的作用是:告诉浏览器,即使页面卸载(跳转/关闭),也要尽量把请求完成。

使用方式非常简单:

document.getElementById('link').addEventListener('click', (e) => {
  fetch("/log", {
    method: "POST",
    headers: { "Content-Type": "application/json" }, 
    body: JSON.stringify({ name: 'FedJavaScript' }),
    keepalive: true
  });
});

好处是:

  • 不需要 preventDefault,也不需要自己控制跳转
  • 不会拖慢页面跳转
  • 请求仍然有很高的完成率

📌 4. 其他可选方案

除了 fetch keepalive,还有一些备选方案:

  • navigator.sendBeacon
    专门为这种「页面卸载时发送少量数据」设计,天然支持后台发送,不会影响跳转。

    navigator.sendBeacon("/log", JSON.stringify({ name: 'FedJavaScript' }));
    
  • Service Worker 缓存再转发
    更复杂的方案,把请求写入 Service Worker 缓存,待网络空闲时再补发。


🎯 总结

  • 页面跳转时,普通 fetch 请求可能被浏览器取消
  • await fetch 能保证请求,但会阻塞跳转,影响体验
  • 推荐方案:使用 fetchkeepalive: true 或者 navigator.sendBeacon
  • 在现代前端开发中,这已经是埋点、日志上报的标准实践

所以,下一次你在写日志上报时,记得打开 keepalive,让数据更可靠、用户体验更流畅 🚀

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>日志上报 Demo</title>
</head>
<body>
  <h1>日志上报 Demo</h1>
  <p>
    点击下面的链接时,会先发送日志,再跳转到百度:
  </p>
  <a id="link" href="https://www.baidu.com">跳转到百度</a>

  <script>
    function sendLog(data) {
      // 优先使用 sendBeacon
      if (navigator.sendBeacon) {
        const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
        navigator.sendBeacon('/log', blob);
      } else {
        // 兜底用 fetch keepalive
        fetch('/log', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(data),
          keepalive: true
        });
      }
    }

    document.getElementById('link').addEventListener('click', (e) => {
      // 在跳转前先发送日志
      sendLog({
        event: 'click_link',
        href: e.target.href,
        time: Date.now()
      });
      // 不阻止跳转,直接继续
    });
  </script>
</body>
</html>

复制全文 生成海报 前端开发 Web技术 用户体验 数据上报

推荐文章

使用临时邮箱的重要性
2025-07-16 17:13:32 +0800 CST
Golang中国地址生成扩展包
2024-11-19 06:01:16 +0800 CST
Git 常用命令详解
2024-11-18 16:57:24 +0800 CST
使用 sync.Pool 优化 Go 程序性能
2024-11-19 05:56:51 +0800 CST
Go 如何做好缓存
2024-11-18 13:33:37 +0800 CST
Nginx 防止IP伪造,绕过IP限制
2025-01-15 09:44:42 +0800 CST
Vue3 中提供了哪些新的指令
2024-11-19 01:48:20 +0800 CST
java MySQL如何获取唯一订单编号?
2024-11-18 18:51:44 +0800 CST
实现微信回调多域名的方法
2024-11-18 09:45:18 +0800 CST
快速提升Vue3开发者的效率和界面
2025-05-11 23:37:03 +0800 CST
pip安装到指定目录上
2024-11-17 16:17:25 +0800 CST
404错误页面的HTML代码
2024-11-19 06:55:51 +0800 CST
Vue3中的Slots有哪些变化?
2024-11-18 16:34:49 +0800 CST
CSS实现亚克力和磨砂玻璃效果
2024-11-18 01:21:20 +0800 CST
Elasticsearch 监控和警报
2024-11-19 10:02:29 +0800 CST
开源AI反混淆JS代码:HumanifyJS
2024-11-19 02:30:40 +0800 CST
一些实用的前端开发工具网站
2024-11-18 14:30:55 +0800 CST
npm速度过慢的解决办法
2024-11-19 10:10:39 +0800 CST
程序员茄子在线接单