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

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

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

推荐文章

PHP 的生成器,用过的都说好!
2024-11-18 04:43:02 +0800 CST
Go中使用依赖注入的实用技巧
2024-11-19 00:24:20 +0800 CST
Redis和Memcached有什么区别?
2024-11-18 17:57:13 +0800 CST
Vue 中如何处理父子组件通信?
2024-11-17 04:35:13 +0800 CST
Vue3 结合 Driver.js 实现新手指引
2024-11-18 19:30:14 +0800 CST
黑客帝国代码雨效果
2024-11-19 01:49:31 +0800 CST
前端项目中图片的使用规范
2024-11-19 09:30:04 +0800 CST
vue打包后如何进行调试错误
2024-11-17 18:20:37 +0800 CST
ElasticSearch 结构
2024-11-18 10:05:24 +0800 CST
PHP 压缩包脚本功能说明
2024-11-19 03:35:29 +0800 CST
使用 Git 制作升级包
2024-11-19 02:19:48 +0800 CST
api远程把word文件转换为pdf
2024-11-19 03:48:33 +0800 CST
解决 PHP 中的 HTTP 请求超时问题
2024-11-19 09:10:35 +0800 CST
前端开发中常用的设计模式
2024-11-19 07:38:07 +0800 CST
手机导航效果
2024-11-19 07:53:16 +0800 CST
XSS攻击是什么?
2024-11-19 02:10:07 +0800 CST
Nginx rewrite 的用法
2024-11-18 22:59:02 +0800 CST
Vue3中的v-model指令有什么变化?
2024-11-18 20:00:17 +0800 CST
利用图片实现网站的加载速度
2024-11-18 12:29:31 +0800 CST
程序员茄子在线接单