POST不同JSON内容时Fetch请求6次后失败的问题求助
我通过Fetch API向Node.js服务器POST包含{pageNumber, UsersPerPage}的JSON来获取用户列表,页面有两个按钮,点击时会将自身文本作为pageNumber发送POST请求,但交替点击6次后,Fetch API抛出错误:Uncaught (in promise) TypeError: Failed to fetch at HTMLDivElement.,且后续请求在网络面板显示为pending,无法完成。前5次请求均正常,已尝试设置Access-Control-Allow-Origin、no-cors头,以及使用async/await,问题仍存在。
前端代码
side1.addEventListener('click',()=>{ fetch('/api/users/stats/side1/post',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ pageNum:side1.textContent, userPerPage:userPerSide }) }); fetch('/api/users/stats/side1/get').then(x=>x.text()).then(Txt=>{ display.innerHTML=Txt; }); }); side2.addEventListener('click', ()=>{ fetch('/api/users/stats/side2/post',{ method:'POST' , headers:{'Content-Type':'application/json'}, body:JSON.stringify({ pageNum:side2.textContent, userPerPage:userPerSide }) }); fetch('/api/users/stats/side2/get').then(x=>x.text()).then(Txt=>{ display.innerHTML=Txt; }); });
后端代码
var retVal; app.post('/api/users/stats/side1/post',(req,res)=>{ if(req.body){ const {pageNum,userPerPage}=req.body; retVal=GetPage(Number(pageNum),Number(userPerPage)); } }); app.post('/api/users/stats/side2/post',(req,res)=>{ if(req.body){ const {pageNum,userPerPage}=req.body; retVal=GetPage(Number(pageNum),Number(userPerPage)); } }); app.get('/api/users/stats/side1/get',(req,res)=>{ res.setHeader('content-type', 'text/plain'); res.send(retVal); }); app.get('/api/users/stats/side2/get',(req,res)=>{ res.setHeader('content-type', 'text/plain'); res.send(retVal); });
问题分析与解决方案
核心问题1:POST请求未响应导致连接耗尽
后端的POST接口没有向客户端返回任何响应(res.send/res.end),浏览器会一直等待服务器响应,TCP连接无法释放。浏览器对同一域名的并发连接数有限制(通常是6个),当积累到6个未关闭的连接后,新的请求就会进入pending状态,最终触发Failed to fetch错误。
修复:给POST接口添加响应
修改后端POST路由,处理完逻辑后返回响应:
app.post('/api/users/stats/side1/post',(req,res)=>{ if(req.body){ const {pageNum,userPerPage}=req.body; retVal=GetPage(Number(pageNum),Number(userPerPage)); res.status(200).send('ok'); // 添加响应 } else { res.status(400).send('invalid data'); // 处理异常情况 } }); app.post('/api/users/stats/side2/post',(req,res)=>{ if(req.body){ const {pageNum,userPerPage}=req.body; retVal=GetPage(Number(pageNum),Number(userPerPage)); res.status(200).send('ok'); // 添加响应 } else { res.status(400).send('invalid data'); } });
核心问题2:POST与GET请求无顺序保证
当前代码中POST和GET是并行发送的,可能出现GET请求先于POST完成,导致获取到的retVal还是旧值。需要保证POST完成后再发送GET请求。
修复:用async/await保证请求顺序
修改前端点击事件:
side1.addEventListener('click', async ()=>{ // 先等待POST请求完成 await fetch('/api/users/stats/side1/post',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ pageNum:side1.textContent, userPerPage:userPerSide }) }); // 再发送GET请求 const response = await fetch('/api/users/stats/side1/get'); const Txt = await response.text(); display.innerHTML=Txt; }); side2.addEventListener('click', async ()=>{ await fetch('/api/users/stats/side2/post',{ method:'POST' , headers:{'Content-Type':'application/json'}, body:JSON.stringify({ pageNum:side2.textContent, userPerPage:userPerSide }) }); const response = await fetch('/api/users/stats/side2/get'); const Txt = await response.text(); display.innerHTML=Txt; });
额外优化:解决全局变量retVal的并发风险
多个请求同时修改全局变量retVal会导致数据错乱,比如快速点击两个按钮时,后一个POST会覆盖前一个的结果,GET拿到错误数据。可以简化流程,让POST直接返回处理后的结果:
后端优化:POST直接返回结果
app.post('/api/users/stats/side1',(req,res)=>{ if(req.body){ const {pageNum,userPerPage}=req.body; const result = GetPage(Number(pageNum),Number(userPerPage)); res.setHeader('content-type', 'text/plain'); res.send(result); } else { res.status(400).send('invalid data'); } }); app.post('/api/users/stats/side2',(req,res)=>{ if(req.body){ const {pageNum,userPerPage}=req.body; const result = GetPage(Number(pageNum),Number(userPerPage)); res.setHeader('content-type', 'text/plain'); res.send(result); } else { res.status(400).send('invalid data'); } });
前端对应修改:只发送一次POST请求
side1.addEventListener('click', async ()=>{ const response = await fetch('/api/users/stats/side1',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ pageNum:side1.textContent, userPerPage:userPerSide }) }); const Txt = await response.text(); display.innerHTML=Txt; }); side2.addEventListener('click', async ()=>{ const response = await fetch('/api/users/stats/side2',{ method:'POST' , headers:{'Content-Type':'application/json'}, body:JSON.stringify({ pageNum:side2.textContent, userPerPage:userPerSide }) }); const Txt = await response.text(); display.innerHTML=Txt; });
内容的提问来源于stack exchange,提问作者Bell Flo

