你可能遇到过这种让人怀疑人生的现象:
同一个页面里几乎同时发出两个请求 A 和 B
在Stateful的 BSP(或任何“绑定会话”的服务端)里,本来应该按顺序串行处理
- A 先跑 3 秒
- B 自己只要 2 秒,但要等 A 完成后再跑,所以总耗时约 5 秒
结果你把发送方式从 jQuery Ajax 换成ES6 Fetch后,Chrome Network 里却看到:
- A、B 竟然同时被处理
- 甚至 B 先返回,控制台也先打印 B 的结果
这不是服务器突然变强了,而是——你无意间把请求从“有会话”变成“无会话”了。
1. 现象复现:同样两次请求,行为完全不同
jQuery(XMLHttpRequest)版本:符合“Stateful 串行”的预期
同一会话下的两个请求会被服务器端顺序处理:
- 两个请求几乎同一时刻发出
- B 在服务器端会等待 A 完成
- 所以 B 的总耗时 = 等待时间(约 3 秒)+ 自己处理时间(约 2 秒)≈ 5 秒
Fetch 版本:看起来像“并发”
你用类似下面的代码:
<script>functionwrapperOnFetch(url){fetch(url).then(r=>r.json()).then(json=>console.log(url+":"+json.message));}functionfire(){wrapperOnFetch("first.json");wrapperOnFetch("second.json");}</script>结果却变成:
- A、B 在服务器端并行处理
- B 2 秒就返回了,不再等 A
2. 真正原因:Fetch 请求里没带上关键的 Session Cookie
要理解这个问题,关键在一句话:
Stateful 的“串行”,是建立在“同一个会话”的前提上的。
你对比两种方式发出的请求头,会发现差异集中在 Cookie 上:
- jQuery/XHR 发送的请求里,带了会话 Cookie(例如
sap-contextid) - Fetch 默认情况下(尤其在“非严格同源”的场景里)可能不会带上该 Cookie
- 服务器端一看:咦?没有会话标识
👉 那就当成“新会话”或“无状态调用”处理
👉 于是 A、B 就不再被同一会话的锁/队列约束,自然能并行
所以你看到的“Fetch 导致并发”,本质上是:
你发出的已经不是同一个 session 上的两个请求了。
3. 为什么 jQuery 会带 Cookie,而 Fetch 可能不带?
简单说:两者对“凭证(credentials)”的默认策略不同,且 Fetch 更严格、更显式。
XHR/jQuery(同源时)
浏览器会自动携带同源 Cookie(不需要你写任何配置)。
Fetch
Fetch 有个非常关键的选项:credentials,用来决定是否携带 Cookie / HTTP 认证信息。
在很多会导致“看似同源、实则不完全同源”的情况下(比如端口不同、协议不同、子域名不同、被当作跨域 CORS、反向代理路径映射等),如果你不显式声明,Cookie 可能不会按你期待的方式送出去。
4. 正确修复:显式启用 credentials
把 Fetch 改成:
functionwrapperOnFetch(url){// 允许携带 session cookiefetch(url,{credentials:"include"}).then(r=>r.json()).then(json=>console.log(url+":"+json.message));}改完后你会看到:
sap-contextid等会话 Cookie 出现在请求里- 服务器把 A、B 识别为同一会话
- Stateful BSP 再次表现为串行处理(B 等 A)
5. 额外提醒:跨域时只写 include 还不够
如果你的请求属于跨域(哪怕只是不同子域/端口导致浏览器判定为跨域),除了前端credentials:"include",后端还必须满足典型的“允许带凭证的 CORS”条件:
Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin不能是*,必须是具体 origin- 以及现代浏览器的 Cookie
SameSite限制(跨站通常要求SameSite=None; Secure)
否则你会遇到“我写了 include 但 Cookie 还是不发 / 不生效”。
6. 排查思路:别猜,直接看 Network 面板
遇到类似问题,最有效的排查顺序是:
- 在 Chrome DevTools → Network → 选中请求
- 看 Request Headers 里有没有
Cookie: - 对比两种请求方式(XHR vs Fetch)是否携带了会话标识(如
sap-contextid) - 再判断“服务器为什么把它们当成不同会话”
总结
这类“换了 Fetch 就变并发”的问题,通常不是 Fetch 更快,而是:
- Cookie 没带上 → 会话断了 → Stateful 约束消失 → 请求并发化
解决方案也很明确:
- 需要会话一致性时:
fetch(url, { credentials: "include" })
如果你愿意的话,你把你实际请求的完整 URL(包含协议、域名、端口)以及你看到的sap-contextid的 Cookie 属性(尤其是 SameSite/Domain/Path)贴出来,我也可以帮你判断:你这里到底是“同源被误判为跨域”,还是“SameSite 把 Cookie 拦了”。