Promise.all阻塞Fetch调用,如何实现aggregateSearch非阻塞执行?
我通过FETCH API调用远程接口,代码实现如下:
document.addEventListener("DOMContentLoaded", () => { loadCart(); aggregateSearch("france", ["api1", "api2"]); }); const aggregateSearch = async (term, AvailableApis) => { console.log("aggregateSearch") await Promise.all(AvailableApis.map((api) => { fetchApi(api, term); })) }; const fetchApi = async (api, term) => { console.log("fetchApi"); const aggregateRawResponse = await fetch('aggregate.php', { method: 'POST', body: JSON.stringify({ source: api, term: term }) }); const aggregateResponse = await aggregateRawResponse.json(); console.log('response from API done'); document.getElementById('myDiv' + api)?.innerHTML = aggregateResponse.formattedResponse; } const updateCartCount = async () => { console.log("update cartcount"); // 该行已执行 const fetchNbPhotos = await fetch("count.php"); const data = await fetchNbPhotos.json(); console.log("cart count done"); document.getElementById('cartCounter').innerHTML = parseInt(data["nb"]); // 仅在aggregateSearch完成后执行 } const loadCart = () => { console.log("start loading cart"); fetch('cart.php') .then((response) => { return response.text(); }) .then((html) => { console.log("loading cart is done updating cart count"); document.getElementById('cart')?.innerHTML = html updateCartCount(); }); }
控制台输出:
start loading cart aggregateSearch fetchApi loading cart is done updating cart count update cartcount cart count done response from API done
API内容获取功能运行正常,但将购物车内容加载到cart容器、更新cartCounter容器的操作,需等待aggregateSearch执行完成后才会进行。尝试将loadCart改为async/await写法,未解决问题。
如何让aggregateSearch实现非阻塞,在API请求处于等待状态时就能更新cartCounter容器?
编辑
尝试将aggregateSearch修改为如下形式:
const aggregateSearch = async (term, AvailableApis) => { console.log("aggregateSearch") await Promise.all(AvailableApis.map((api) => { setTimeout(() => fetchApi(api, fulltext, []), 100); //fetchApi(api, fulltext, []); })) };
此时cartCounter容器能立即更新。
编辑2
可复现场景中,控制台输出:
"start loading cart" "aggregateSearch" "update cartcount" "response from API done" "cart count done"
由于fetchApi设置了2000ms延迟,loadCart和updateCartCount均设置为100ms延迟,预期控制台输出应为:
"start loading cart" "aggregateSearch" "update cartcount" "cart count done" "response from API done"
核心问题有两个:前端代码的Promise处理错误,以及可能存在的PHP会话锁导致请求串行。
1. 修正前端Promise处理
aggregateSearch中的Promise.all调用存在错误:map的回调函数没有返回fetchApi的Promise,导致Promise.all等待的是一堆undefined,虽然Promise.all会立即完成,但fetchApi的同步执行部分会抢占主线程,同时后续的API请求可能因为服务器端的串行处理被阻塞。
修正后的aggregateSearch代码:
const aggregateSearch = async (term, AvailableApis) => { console.log("aggregateSearch") // 关键:返回fetchApi的Promise给map,确保Promise.all正确追踪异步任务 await Promise.all(AvailableApis.map((api) => fetchApi(api, term))); };
修正后,Promise.all会正确等待所有API请求完成,但await会主动让出主线程,不会阻塞购物车更新的异步任务执行。
2. 解决PHP会话锁问题(关键原因)
如果后端是PHP,默认情况下会话使用文件锁,同一个会话的多个请求会被串行处理——也就是说,当aggregate.php的请求先发起并持有会话锁时,cart.php和count.php的请求必须等待它释放锁才能执行,这就导致购物车更新被阻塞。
解决办法是在每个PHP脚本中,完成会话数据读取后立即关闭会话,释放锁:
session_start(); // 读取所需的会话数据(比如用户ID等) $userId = $_SESSION['user_id']; // 立即关闭会话,释放锁 session_write_close(); // 后续的业务逻辑处理
这样多个请求就能并行处理,购物车的更新请求不会被API请求阻塞。
为什么加setTimeout能临时生效?
setTimeout会把fetchApi的调用推迟到下一个事件循环,让购物车的请求先发起并抢占会话锁,从而避开了串行阻塞的问题,但这只是临时的规避方案,不是根本解决办法。
内容的提问来源于stack exchange,提问作者akio

