使用Fetch API循环获取多URL HTML时键值对匹配错误如何修复
修复for循环中Fetch API异步导致的键值不匹配问题
你在for循环中使用Fetch API批量获取URL的HTML数据,初始pos_links_info结构如下:
link 0 : contains URL of first webpage link 1 : contains URL of second webpage...//and so on
但实际输出出现键值对错位,比如title 0存储的是第二个网页的标题,body 0 x也是第二个网页的内容,和预期结构不符。
原代码
var sl_no = 0; for(const [key,value] of Object.entries(pos_links_info)){ //Here value is the URL of that website fetch('https://api.codetabs.com/v1/proxy?quest='+value) .then( function (response) { if (response.status !== 200) { console.log('Unable to fetch data from url link: ' +response.status); return; } response.text().then(function(data) { var date = new Date(); var parser = new DOMParser(); var htmldoc = parser.parseFromString(data, "text/html"); var title; if(htmldoc.getElementsByTagName("h1")[0] != null) title = htmldoc.getElementsByTagName("h1")[0].innerText; else title = htmldoc.getElementsByTagName("title")[0].innerText; if(title != ""){ pos_links_info["title"+" "+sl_no]=title.trim(); } if(htmldoc.getElementsByTagName("img") != null){ for(var l=0; l<htmldoc.getElementsByTagName("img").length; l++){ if(htmldoc.getElementsByTagName("img")[l].src.includes(value.split("https://")[1].split("/")[0]) && htmldoc.getElementsByTagName("img")[l].src.includes(date.getFullYear().toString())){ pos_links_info["image"+" "+sl_no] = htmldoc.getElementsByTagName("img")[l].src.trim(); break; } } } if(htmldoc.getElementsByTagName("p") != null){ pos_count_body = htmldoc.getElementsByTagName("p").length-1; for(var l=0; l<htmldoc.getElementsByTagName("p").length-1; l++){ pos_links_info["body"+" "+sl_no+" "+l] = htmldoc.getElementsByTagName("p")[l].innerText.trim(); } } }) } ) .catch(function(err) { console.log('Fetch Error :-S', err); }); sl_no += 1; }
问题原因
核心是异步操作的闭包陷阱:Fetch是异步任务,then回调函数会在循环结束后才执行,此时sl_no已经完成了所有递增操作,所有回调都会共享同一个sl_no的最终值,导致数据被错误地存入了后续的键中。
修复方案
方案1:捕获当前迭代的索引
在每次循环中创建独立的变量保存当前的sl_no,避免回调共享同一个变量:
var sl_no = 0; for(const [key,value] of Object.entries(pos_links_info)){ // 保存当前迭代的索引,每个回调绑定独立的变量 const currentSlNo = sl_no; fetch('https://api.codetabs.com/v1/proxy?quest='+value) .then(function (response) { if (response.status !== 200) { console.log('Unable to fetch data from url link: ' +response.status); return; } response.text().then(function(data) { var date = new Date(); var parser = new DOMParser(); var htmldoc = parser.parseFromString(data, "text/html"); var title; if(htmldoc.getElementsByTagName("h1")[0] != null) title = htmldoc.getElementsByTagName("h1")[0].innerText; else title = htmldoc.getElementsByTagName("title")[0].innerText; if(title != ""){ // 使用currentSlNo代替sl_no pos_links_info["title"+" "+currentSlNo] = title.trim(); } // 优化判断:getElementsByTagName始终返回集合,判断length更准确 if(htmldoc.getElementsByTagName("img").length > 0){ for(var l=0; l<htmldoc.getElementsByTagName("img").length; l++){ if(htmldoc.getElementsByTagName("img")[l].src.includes(value.split("https://")[1].split("/")[0]) && htmldoc.getElementsByTagName("img")[l].src.includes(date.getFullYear().toString())){ pos_links_info["image"+" "+currentSlNo] = htmldoc.getElementsByTagName("img")[l].src.trim(); break; } } } if(htmldoc.getElementsByTagName("p").length > 0){ for(var l=0; l<htmldoc.getElementsByTagName("p").length-1; l++){ pos_links_info["body"+" "+currentSlNo+" "+l] = htmldoc.getElementsByTagName("p")[l].innerText.trim(); } } }) }) .catch(function(err) { console.log('Fetch Error :-S', err); }); sl_no += 1; }
方案2:使用async/await同步化异步操作
将循环改为异步循环,确保每次Fetch完成后再执行下一次迭代,彻底避免闭包问题:
var sl_no = 0; // 自执行异步函数,支持await语法 (async function() { for(const [key,value] of Object.entries(pos_links_info)){ try { // 等待Fetch请求完成 const response = await fetch('https://api.codetabs.com/v1/proxy?quest='+value); if (response.status !== 200) { console.log('Unable to fetch data from url link: ' + response.status); sl_no += 1; continue; } // 等待获取文本数据 const data = await response.text(); const date = new Date(); const parser = new DOMParser(); const htmldoc = parser.parseFromString(data, "text/html"); let title; if(htmldoc.getElementsByTagName("h1")[0] != null) title = htmldoc.getElementsByTagName("h1")[0].innerText; else title = htmldoc.getElementsByTagName("title")[0].innerText; if(title != ""){ pos_links_info["title"+" "+sl_no] = title.trim(); } if(htmldoc.getElementsByTagName("img").length > 0){ for(var l=0; l<htmldoc.getElementsByTagName("img").length; l++){ if(htmldoc.getElementsByTagName("img")[l].src.includes(value.split("https://")[1].split("/")[0]) && htmldoc.getElementsByTagName("img")[l].src.includes(date.getFullYear().toString())){ pos_links_info["image"+" "+sl_no] = htmldoc.getElementsByTagName("img")[l].src.trim(); break; } } } if(htmldoc.getElementsByTagName("p").length > 0){ for(var l=0; l<htmldoc.getElementsByTagName("p").length-1; l++){ pos_links_info["body"+" "+sl_no+" "+l] = htmldoc.getElementsByTagName("p")[l].innerText.trim(); } } // 只有当前URL处理完成后才递增索引 sl_no += 1; } catch(err) { console.log('Fetch Error :-S', err); sl_no += 1; } } })();
额外优化点
- 原代码中
htmldoc.getElementsByTagName("img") != null的判断冗余,getElementsByTagName始终返回HTMLCollection对象,即使没有匹配元素也是空集合,改为判断length > 0更准确。 - 嵌套的
response.text().then()可以改为链式调用,方案2中用await实现了更整洁的写法。
内容的提问来源于stack exchange,提问作者user19835820
相关产品推荐
相关产品推荐

