Fetch API打印Promise结果顺序异常的原因及解决方法
为什么获取的HTML内容打印顺序与URL顺序不一致?如何保证顺序?
问题重现
我通过以下代码批量获取两个网页的HTML内容,但发现打印结果中url2的数据先输出,url1的后输出,和预期顺序相反:
主代码
const links = {"url1" : "https://.......", "url2" : "https://......"}; var responses = []; for(const [key,value] of Object.entries(links)){ let resp = fetch('https://api.codetabs.com/v1/proxy?quest='+value) responses.push(resp); } Promise.all(responses) .then( htmlfiles =>{ htmlfiles.forEach(file=>{ file.text().then(function(data){ gethtmldata(data); }) }) })
解析函数
function gethtmldata(html_data){ var parser = new DOMParser(); var htmldoc = parser.parseFromString(html_data, "text/html"); console.log(htmldoc); // 先显示url2的数据,再显示url1的 }
原因分析
Promise.all(responses)确实会按传入Promise的顺序返回resolved结果,也就是htmlfiles数组的第一个元素对应url1的响应,第二个对应url2的响应。但问题出在后续的forEach循环里:
- 每个
file.text()都是独立的异步操作,它们的完成速度取决于网络响应的快慢,和数组顺序无关 - 哪个
text()先完成,就会先调用gethtmldata并打印结果,所以最终顺序会乱
修复方案
要保证顺序,需要把text()的异步操作也纳入Promise.all的控制范围,让返回的结果直接是解析好的文本,这样遍历的时候就能严格按原URL顺序处理:
修改后的代码
const links = {"url1" : "https://.......", "url2" : "https://......"}; const requests = []; for(const [key, value] of Object.entries(links)){ // 把fetch和text()连起来,形成一个完整的Promise链 const request = fetch('https://api.codetabs.com/v1/proxy?quest='+value) .then(resp => resp.text()); requests.push(request); } Promise.all(requests) .then(htmlTexts => { // htmlTexts的顺序和原links的URL顺序完全一致 htmlTexts.forEach(text => { gethtmldata(text); }); });
另一种直观写法(使用async/await)
const links = {"url1" : "https://.......", "url2" : "https://......"}; async function fetchInOrder() { // 收集所有包含text解析的请求Promise const requests = Object.values(links).map(url => fetch(`https://api.codetabs.com/v1/proxy?quest=${url}`).then(resp => resp.text()) ); // 等待所有请求完成,结果顺序与原数组完全匹配 const htmlTexts = await Promise.all(requests); // 按顺序处理每个文本 htmlTexts.forEach(text => gethtmldata(text)); } fetchInOrder();
这样修改后,htmlTexts数组的顺序和links中URL的顺序完全对应,遍历调用gethtmldata时就会先打印url1的内容,再打印url2的内容。
内容的提问来源于stack exchange,提问作者user19835820
相关产品推荐
相关产品推荐

