任务: 用户离开当前页面时,发送相关数据给服务器。
直观做法
<a href="/some-other-page" id="link">Go to Page</a>
<script>
document.getElementById('link').addEventListener('click', (e) => {
fetch("/log", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
some: "data"
})
});
});
</script>
监听用户页面跳转按钮,然后绑定点击事件,完成POST请求。
这样行不通,原因有两点:
- 用户离开页面的方式未必是点击跳转,也可能是直接关闭页面或者输入其它网址。
- 即使是按预想点击跳转,这个POST请求也未必能发送出去。
为什么POST请求发不出去
上面这个POST请求,属于异步请求。也就是说,请求不会立即执行,而是先放置在任务队列中。
为了优化性能,浏览器设计了web页面生命周期。在跳转至别的页面过程中,页面会历经多个阶段。当进入 Terminated 阶段时,浏览器会清空任务队列,也就是说任务队列中尚未被执行的任务不再执行,正在执行的任务有可能被杀死。
所以,这个POST请求有可能直接被清空,或者中止执行。
可以用同步请求吗
可以。但有两个缺点:
- 会阻塞主线程。
- 新版浏览器不支持。
可以阻塞跳转吗
让用户完成发送后,再跳转至新的页面。
document.getElementById('link').addEventListener('click', async (e) => {
e.preventDefault();
// Wait for response to come back...
await fetch("/log", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
some: 'data'
}),
});
// ...and THEN navigate away.
window.location = e.target.href;
});
同样有两个缺点:
- 增加用户跳转延时。
- 无法监听非点击式跳转。
推荐做法
1. 监听'visibilitychange'事件,而不是点击事件。
这样,能够监听其它离开页面的行为。
2. 用以下两种请求方式
2.1 用 fetch()
提供的 keepalive
选项,确保请求能够发送。
<a href="/some-other-page" id="link">Go to Page</a>
<script>
document.getElementById('link').addEventListener('click', (e) => {
fetch("/log", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
some: "data"
}),
keepalive: true
});
});
</script>
2.2 最佳做法navigator.sendBeacon()
这个接口不抢占资源,用最低的优先级,但确保请求会被发送。不过,这个API不能自定义header, 为了发送 “application/json”格式的数据,需要利用Blob
技巧。
document.addEventListener('visibilitychange', function logData() {
if (document.visibilityState === 'hidden') {
const blob = new Blob(
[JSON.stringify({ some: "data" })],
{ type: 'application/json; charset=UTF-8' }
);
navigator.sendBeacon('/log', blob));
}
});
网友评论