登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

Fetch keepalive 请求有什么大小和生命周期边界

来源:17golang原创

时间:2026-11-15 01:02:57 172浏览 收藏

页面跳转或关闭前,前端经常还想补发一次埋点、会话状态或轻量日志。fetch(url, { keepalive: true }) 可以让请求在发起它的页面卸载后继续发送,但它不是“永不超时”的通道,也不是把 HTTP 连接永久挂住。真正需要记住的有两条:请求体要按字节控制,多个尚未完成的 keepalive 请求会共同消耗有限额度;页面消失后只能提高送达机会,不能保证服务器一定完成业务处理。

官方资料:https://developer.mozilla.org/en-US/docs/Web/API/RequestInit https://fetch.spec.whatwg.org/

要点速览
  • Fetch 的 keepalive 与 HTTP 的 Keep-Alive 不是一回事,前者解决的是页面卸载时的请求生命周期。
  • 实战上把单次事件控制在 60 KiB 以内更稳妥;规范检查的是同一 fetch group 中未完成 keepalive 请求体的累计量,硬边界是 64 KiB。
  • 它适合分析事件、离开记录等可容忍丢失的轻量数据;订单提交、扣款和关键状态变更仍应走正常可靠接口。

Fetch keepalive 与 HTTP Keep-Alive 解决的不是同一个问题

前端代码里的 keepalive: true 是 Fetch API 的请求选项。它的目标是:文档开始卸载后,用户代理不要因为原页面环境即将消失就立刻放弃这次请求。HTTP 头里的 Keep-Alive 则属于连接管理语义,讨论的是连接空闲多久、最多承载多少请求;不要把两个同名词混成一个开关。

因此,下面的场景适合考虑 keepalive:用户点击离开前上报一次阅读进度、记录一次导航来源、发送小型诊断事件。它不适合承载大文件、长轮询、需要持续响应的交互,也不应成为关键写入唯一的成功凭据。

Fetch keepalive 的 64 KiB 不是单请求孤立额度

Fetch keepalive 请求体、fetch group 与 64 KiB body budget 的静态关系说明图
图1:keepalive 请求体配额的静态说明图,64 KiB 约束关注未完成请求的 body 累计量。

MDN 给出的工程记忆点是 keepalive 请求体限制为 64 KiB。Fetch 标准的算法更精确:当请求有可计算的 Content-Length 时,浏览器会把本次请求体长度与同一 fetch group 中仍未完成的 keepalive 请求体长度相加;总和超过 64 KiB,就返回 network error。也就是说,连续快速发出多个小请求,也可能因为累计超界而失败。

这里的 64 KiB 约束讨论的是请求体,不是响应体大小,也不是“每个请求都独享 64 KiB”。JSON 还要按 UTF-8 字节计算,中文字符不能简单按 JavaScript 的字符串长度估算。生产实现通常给自己留出余量,例如把单个事件控制在 60 KiB 以下,并避免在离开瞬间并发堆积多条 keepalive 请求。

页面离开时它能保活什么、不能保证什么

页面脚本、Request.keepalive、环境对象、网络和服务器之间的生命周期边界说明图
图2:页面卸载与 keepalive 生命周期的静态说明图,保活请求不等于服务器必然完成处理。

触发点可以放在应用已经决定离开的时刻,或使用 pagehide 做最后一次轻量上报。示例先计算 UTF-8 字节数,再调用 keepalive;60 * 1024 是人为保留的工程余量,不是把规范上限改成了 60 KiB。

function sendExitEvent(detail) {
  // JSON 字符串的字符数不等于 UTF-8 字节数,先按真实字节计量。
  const body = JSON.stringify({ type: "page_exit", detail });
  const bodyBytes = new TextEncoder().encode(body).byteLength;

  // 给同一页面中其他未完成的 keepalive 请求留出余量。
  if (bodyBytes > 60 * 1024) return;

  // 不等待响应;页面离开后 Promise 可能再也没有可见的消费方。
  void fetch("/telemetry/exit", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body,
    keepalive: true
  }).catch(() => {
    // 离开场景允许丢失,避免未处理的 rejected Promise 干扰页面日志。
  });
}

window.addEventListener("pagehide", () => {
  // 事件应可重复接收,服务端用 eventId 或幂等键去重。
  sendExitEvent({ eventId: crypto.randomUUID() });
});

这个写法只提高了请求在页面卸载后继续传输的机会。设备断网、浏览器进程被系统回收、请求被策略拦截、服务端超时或返回错误时,数据仍可能丢失;浏览器也不会替你把一次分析事件变成事务。服务端要把这类接口设计成幂等、快速响应,并接受重复或缺失。

按数据重要性选择 keepalive、sendBeacon 或正常接口

场景更合适的选择判断理由
小型离开事件,需要自定义方法或读取响应fetch + keepalive:true保留 Fetch 的请求配置和 Promise,但仍要接受尽力而为。
只想在页面离开时发送少量分析数据navigator.sendBeacon()接口语义更直接,调用方通常不需要处理响应。
订单、支付、权限、库存等关键状态正常接口 + 服务端幂等与重试不能把页面卸载后的 best effort 当成业务成功凭据。

还有一个常见误区:把 keepalive 写上,就以为跨域、凭据、CORS 和服务端处理都自动变得可靠。它只改变请求生命周期的一部分,仍要遵守 Fetch 的模式、凭据策略和服务器端接口约束。若事件很重要,下一次打开页面时补偿上报通常比在卸载回调里不断加大请求体更可控。

相关问题

64 KiB 是请求体还是响应体?

这里的 keepalive 配额主要针对请求体及其未完成累计量,不是响应体下载上限。不要用它发送大批量日志,也不要据此推断响应一定能读完。

把 JSON 压缩后就一定能发送吗?

不一定。压缩后的传输方式、实际请求体、并发中的其他 keepalive 请求和浏览器策略都可能影响结果。应用层仍应按字节设上限,并在服务端保留幂等处理。

为什么不用 unload 事件等待 fetch 完成?

卸载阶段不适合等待异步响应。keepalive 的价值正是让请求脱离页面销毁节奏继续尽力发送;若业务必须确认成功,应在页面仍然可交互时完成正常请求。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>