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

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

🚀 前端必学技巧:用户离开页面时如何可靠地发送 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技术 用户体验 数据上报

推荐文章

从Go开发者的视角看Rust
2024-11-18 11:49:49 +0800 CST
支付页面html收银台
2025-03-06 14:59:20 +0800 CST
微信内弹出提示外部浏览器打开
2024-11-18 19:26:44 +0800 CST
PHP中获取某个月份的天数
2024-11-18 11:28:47 +0800 CST
markdowns滚动事件
2024-11-19 10:07:32 +0800 CST
JavaScript 实现访问本地文件夹
2024-11-18 23:12:47 +0800 CST
HTML5的 input:file上传类型控制
2024-11-19 07:29:28 +0800 CST
Vue3中如何处理状态管理?
2024-11-17 07:13:45 +0800 CST
15 个你应该了解的有用 CSS 属性
2024-11-18 15:24:50 +0800 CST
Rust 高性能 XML 读写库
2024-11-19 07:50:32 +0800 CST
如何优化网页的 SEO 架构
2024-11-18 14:32:08 +0800 CST
Git 常用命令详解
2024-11-18 16:57:24 +0800 CST
JavaScript中设置器和获取器
2024-11-17 19:54:27 +0800 CST
php 统一接受回调的方案
2024-11-19 03:21:07 +0800 CST
Vue3中怎样处理组件引用?
2024-11-18 23:17:15 +0800 CST
前端如何优化资源加载
2024-11-18 13:35:45 +0800 CST
程序员茄子在线接单