PHP结合AJAX如何实现单请求非同步双响应及前端JS处理方法
结论
单HTTP请求下完全可以实现分阶段返回两类结果,核心是利用HTTP分块传输能力,不需要拆分两个独立请求。如果你的服务端环境存在强制缓冲无法使用分块传输,也可以用异步任务+轮询/长连接的方案实现同等效果。
PHP后端实现要点
要实现同请求分块返回,核心是关闭多层级输出缓冲,每完成一段处理就强制把内容推送到客户端:
- 提前关闭PHP自身的
output_buffering配置,调用ob_end_clean()清空已有缓冲 - 发送分块传输响应头,关闭web服务器层的强制gzip缓冲(部分Nginx环境需要额外设置
header('X-Accel-Buffering: no')避免Nginx缓存输出) - 快速任务处理完成后立刻输出对应内容,调用
ob_flush()和flush()强制推送内容到客户端,不要断开连接 - 后续执行耗时任务,完成后输出第二段结果,再结束请求
参考代码:
<?php ob_end_clean(); // 关闭Nginx缓冲 header('X-Accel-Buffering: no'); header('Transfer-Encoding: chunked'); header('Content-Type: application/json'); header('Connection: keep-alive'); // 处理快速任务,返回数值结果 $quickNumResult = 200; // 替换为你的快速计算逻辑 echo json_encode([ 'stage' => 'quick', 'payload' => $quickNumResult ]) . "\r\n"; ob_flush(); flush(); // 执行耗时任务 set_time_limit(0); // 避免长时间执行超时 sleep(3); // 替换为你的耗时业务逻辑 $longArrayResult = ['list' => [1,2,3,4], 'status' => 'done']; echo json_encode([ 'stage' => 'long_task', 'payload' => $longArrayResult ]) . "\r\n"; ob_flush(); flush(); exit;
前端JavaScript接收实现
常规的fetch/axios默认配置会等待整个响应完全结束才返回结果,需要逐块读取响应流才能拿到分阶段返回的内容,有两种成熟实现方式:
兼容性更好的XMLHttpRequest实现
支持所有主流浏览器,通过onprogress事件监听实时拿到已返回的响应内容:
const xhr = new XMLHttpRequest(); xhr.open('POST', '/your_task_api.php', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8'); let processedIndex = 0; // 记录已处理的内容位置,避免重复解析 xhr.onprogress = function () { const allResp = xhr.responseText; // 截取未处理的新内容 const newContent = allResp.slice(processedIndex); processedIndex = allResp.length; // 拆分数据块,过滤空内容 const chunks = newContent.split('\r\n').filter(item => item.trim()); chunks.forEach(chunk => { try { const res = JSON.parse(chunk); if (res.stage === 'quick') { // 处理第一阶段返回的数值 console.log('快速任务结果:', res.payload); // 此处写你的页面渲染逻辑,比如更新数值展示 return; } if (res.stage === 'long_task') { // 处理第二阶段返回的数组 console.log('耗时任务结果:', res.payload); // 此处写数组渲染、状态更新逻辑 } } catch (err) { // 数据块不完整时解析失败,直接跳过等待下一次进度事件即可 } }); }; xhr.onload = function () { console.log('所有任务处理完成'); }; // 传入你的POST参数 xhr.send('param1=value1¶m2=value2');
现代浏览器的Fetch Streams实现
代码逻辑更清晰,支持Chrome 43+、Firefox 65+、Edge 79+版本:
async function sendTaskRequest() { const resp = await fetch('/your_task_api.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' }, body: 'param1=value1¶m2=value2' }); const reader = resp.body.getReader(); const decoder = new TextDecoder('utf-8'); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const chunks = buffer.split('\r\n').filter(item => item.trim()); // 把最后一段可能不完整的内容放回缓存 buffer = chunks.pop() || ''; chunks.forEach(chunk => { const res = JSON.parse(chunk); if (res.stage === 'quick') { console.log('快速任务结果:', res.payload); // 数值处理逻辑 } if (res.stage === 'long_task') { console.log('耗时任务结果:', res.payload); // 数组处理逻辑 } }); } } sendTaskRequest();
生产环境备选方案
如果你的服务端挂在CDN、反向代理后面,被强制开启了响应缓冲导致分块内容无法实时推送,可以改用更稳定的异步队列方案:
- 第一次AJAX请求只处理快速任务,立刻返回数值结果,同时把耗时任务写入异步任务队列,返回唯一任务ID
- 前端拿到任务ID后,通过短轮询(每隔1-2秒发一次请求查任务状态)或者WebSocket长连接监听任务结果
- 后台消费进程处理完耗时任务后,把结果存入缓存,前端查询到任务完成状态时拉取数组结果即可
该方案不受代理层缓冲限制,稳定性更高,适合大流量线上业务使用。
内容的提问来源于stack exchange,提问作者Frederik Krohn
相关产品推荐
相关产品推荐

