如何处理页面关闭时发送HTTP请求?

简介: 在实际项目开发中,可能会遇到这样的业务问题:如何在用户离开或关闭页面时发送HTTP请求给服务端?可能有人会觉得页面都关闭了,还需要发送什么请求,完全没必要噻。但如果真有这样的业务需求落到自己的头上,那么我们应该如何来实现呢?

在实际项目开发中,可能会遇到这样的业务问题:如何在用户离开或关闭页面时发送HTTP请求给服务端?可能有人会觉得页面都关闭了,还需要发送什么请求,完全没必要噻。但如果真有这样的业务需求落到自己的头上,那么我们应该如何来实现呢?

注:本文章基于Chrome 76,高版本的Chrome浏览器测试效果可能会有差异

关闭或离开页面

可能使用Vue的朋友会比较熟悉beforeDestoryonBeforeUnMounted这两个API,用来处理组件销毁前的事件。其实js中也有类似的方法:beforeunload

beforeunload 会在浏览器关闭页面或刷新页面时触发,可以点击确定按钮关闭或刷新,也可以取消关闭或刷新,使用方法:

window.addEventListener('beforeunload', (event) => {
  // 阻止浏览器默认事件,也就是阻止关闭和刷新页面
  event.preventDefault();
  // chrome浏览器需要设置返回值
  event.returnValue = true;
});

如果是离开页面,应该怎么处理呢?假设我们在页面上有一个链接,点击后会跳转到另一个页面:

<a href="https://baidu.com" id="link">点击跳转</a>
// js
document.getElementById("link").addEventListener('click', (e) => {
    e.preventDefault(); // 阻止浏览器默认事件,点击链接就不会发生跳转
    window.location = e.target.href;
})

明白了这两个问题后,我们再来看接下来的问题

HTTP请求canceled?

js是单线程的,因此网络请求,包括fetch和XMLHttpRequest请求,被设计成是异步且非阻塞的。异步操作有一个好处,就是它不会占用主进程,但是这也会带来问题,如果主进程销毁了,例如页面关闭或者离开当前页面,那么原来异步进行的网络请求可能会被忽略。直观的体现就是我们可以在network中看到请求已经canceled。举个栗子:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <a href="/other.html" id="link">离开页面</a>

    <script>
      document.getElementById("link").addEventListener("click", (e) => {
        fetch("http://localhost:8088/log", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            data: "data",
          })
        });
      });
    </script>
  </body>
</html>

关于这个例子(下同)有几点需要说明:

  • 我们使用的是ES6新增的fetch()发送HTTP请求,而不是额外引入axios,关于fetch的使用可以参考阮一峰老师的教程:Fetch API 教程
  • fetch()中传入的是请求后端接口地址,我们这里使用的是express框架搭建的简易后端,本次案例中未实现,详细的使用后面会有单独的文章介绍
  • 案例模拟的是离开页面的场景,关闭页面需要借助于beforeunloadunload,两者相差不大,掌握原理才是最重要的

执行过程(下同):

  • 打开控制台,并选择network,将网络状态选为slow 3g,这么做的目的是让我们能更清晰的看到执行过程
  • 点击“离开页面”,我们可以看到会有一个fetch请求处于pending状态
  • 然后页面跳转到other.html,可以发现刚才的log请求的状态变为了canceled,也就是被取消了

运行结果:

image-20220625212805843.png

可以看到,我们刚刚的log请求处于被取消的状态,如果是在实际业务场景中,那么就有可能导致我们的业务请求没有能发送到服务端。那么我们应该怎么解决呢?

如何解决这个问题

解决HTTP请求被canceled的问题,常见的一般有这么几个解决方案:

  • async/await
  • fetch + keepalive
  • navigator.sendBeacon()
  • ping

接下来就逐一讨论:

async/await

fetch()接口返回的是一个Promise()对象,因此我们可以等待fetch()接口完成后才执行页面跳转,如果使用的是axios,这里也是一样的:

document.getElementById("link").addEventListener("click", (e) => {
    e.preventDefault(); // 阻止默认跳转行为
    fetch("http://localhost:8088/log", {
        method: "POST",
        headers: {
        "Content-Type": "application/json",
        },
        body: JSON.stringify({
            someData: "222",
        })
    }).then(() => {
        window.location = e.target.href; // 页面跳转
    });
});

或者使用async/await

document.getElementById("link").addEventListener("click", async (e) => {
    e.preventDefault(); // 阻止默认跳转行为
    await fetch("http://localhost:8088/log", {
        method: "POST",
        headers: {
        "Content-Type": "application/json",
        },
        body: JSON.stringify({
            some: "222",
        })
    });// 同步
    window.location = e.target.href; // 页面跳转
});

运行结果:

我们可以明显的看到,虽然点击了链接,但是需要等到请求结束后才会执行跳转,也就是会有一个等待的过程。如果网络请求事件太长,这将会是一个很糟糕的体验。

image-20220625214948827.png

fetch + keepalive

keepalivefetch的一个属性,目的是告诉浏览器,即使页面卸载了,也要在后台保持连接,继续发送数据。用法也比较简单,直接传入true即可,默认是false:

document.getElementById("link").addEventListener("click", (e) => {
    fetch("http://localhost:8088/log", {
        method: "POST", // fetch支持GET、POST、PUT、DELETE的请求方法
        headers: {
        "Content-Type": "application/json",
        }, // 请求头
        body: JSON.stringify({
            some: "222",
        }), // 请求数据
        keepalive: true, // 保持在后台连接
    });
});

使用keepalive是简单且有效的,那如果我们想要追求更简单的方式呢

navigator.sendBeacon()

navigator.sendBeacon()方法可用于通过http post的方式将少量数据异步传输到服务器,它的实现原理和传统的XMLHttpRequest有所区别。使用方式一般有两种:

navigator.sendBeacon(url);
navigator.sendBeacon(url, data);

其中,data表示需要发送的Blob、FormData、ArrayBuffer等类型的数据。使用navigator.sendBeacon()无法自定义请求头部,我们可以借助于Blob对象来简单封装请求头和请求数据:

document.getElementById("link").addEventListener("click", (e) => {
    // 自定义请求头
    // const blob = new Blob([JSON.stringify({ some: "data" })], { type: 'application/json; charset=UTF-8' });
    // navigator.sendBeacon('http://localhost:8088/log', blob);
    // 直接发送
    navigator.sendBeacon('http://localhost:8088/log');
});

ping

这或许是最简单的解决方式了吧,ping包含一个以空格分割的url列表,传入的值为字符串,在超链接(a标签)中使用时,浏览器会在后台发送带有正文ping的POST请求。使用方式如下:

<a href="/other.html" id="link" ping="http://localhost:8088/log">离开页面</a>

直接使用在a标签上面即可,注意一定是有意义的a标签,这样写就不行了:

<a id="link" ping="http://localhost:8088/log">离开页面</a>

兼容性:

image-20220625223130672.png

对比四种方式的优缺点

目前能想到的就这四种,说了这么多,简单总结一下它们的优缺点,其实并没有谁对谁错,只是使用的场景不同。

async/await

  • 需要先阻止浏览器的默认事件,等到请求结束后再执行
  • 由于需要等待网络请求执行完成,因此会导致用户长时间得不到反馈
  • 如果业务场景不在乎等待时间,可以考虑

fetch + keepalive

  • fetch接口自带的属性,无需额外引入
  • 如果请求需要支持GET、POST、PUT、DELETE等,可以选择使用fetch
  • 兼容性较好,除了IE不支持

navigator.sendBeacon()

  • HTTP请求只能是POST请求
  • 发送的数据量少,并且需要更加简洁的API
  • 该请求的优先级较低,不会与其他HTTP请求竞争资源
  • 兼容性较好,除了IE不支持

ping

  • 足够简单,仅依靠HTML就能完成,无需借助JavaScript
  • 不会阻塞页面后续行为,与navigator.sendBeacon()类似;并且支持跨域
  • 目前支持者a标签,其他元素设置ping属性是没有效果的
  • 只能是POST请求,不能发送GET请求
  • 无法自定义请求数据
  • 兼容性很好,除了IE不支持,FireFox默认未启用,需要再FireFox设置中开启

总结

本文总结了前端处理页面关闭时如何保证HTTP请求能顺利发送出去,总的来说有四种方式。每一种方式都有优缺点,需要读者自己权衡如何在实际业务场景种使用。比较推荐的是fetch + keepalivenavigator.sendBeacon()。复现文章中的代码,需要先检查一下浏览器版本以及是否清空了缓存。

相关文章
|
9天前
|
缓存 前端开发 API
|
14天前
|
数据采集 前端开发 算法
Python Requests 的高级使用技巧:应对复杂 HTTP 请求场景
本文介绍了如何使用 Python 的 `requests` 库应对复杂的 HTTP 请求场景,包括 Spider Trap(蜘蛛陷阱)、SESSION 访问限制和请求频率限制。通过代理、CSS 类链接数控制、多账号切换和限流算法等技术手段,提高爬虫的稳定性和效率,增强在反爬虫环境中的生存能力。文中提供了详细的代码示例,帮助读者掌握这些高级用法。
Python Requests 的高级使用技巧:应对复杂 HTTP 请求场景
|
3天前
|
前端开发 JavaScript Java
如何捕获和处理HTTP GET请求的异常
如何捕获和处理HTTP GET请求的异常
|
5天前
|
开发者
HTTP 协议请求方法的发展历程
【10月更文挑战第21天】
|
5天前
|
安全
HTTP 协议的请求方法
【10月更文挑战第21天】
|
5天前
|
缓存 安全 前端开发
HTTP 协议的请求方法在实际应用中有哪些注意事项?
【10月更文挑战第29天】HTTP协议的请求方法在实际应用中需要根据具体的业务场景和需求,合理选择和使用,并注意各种方法的特点和限制,以确保网络通信的安全、高效和数据的一致性。
|
7天前
|
存储 缓存 网络协议
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点,GET、POST的区别,Cookie与Session
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点、状态码、报文格式,GET、POST的区别,DNS的解析过程、数字证书、Cookie与Session,对称加密和非对称加密
|
9天前
|
安全 API 数据安全/隐私保护
常见的HTTP请求方法
【10月更文挑战第25天】这些HTTP请求方法共同构成了客户端与服务器之间交互的基础,使得客户端能够根据不同的需求对服务器资源进行各种操作。在实际应用中,开发者需要根据具体的业务场景和资源的性质选择合适的请求方法来实现客户端与服务器之间的有效通信。
|
13天前
|
存储 安全 网络协议
HTTP 请求方法
【10月更文挑战第22天】HTTP 请求方法
26 2
|
13天前
|
缓存 JSON 安全
HTTP请求发送方法
HTTP请求发送方法【10月更文挑战第22天】
30 2