为何insertRow方法添加空白行?fetch请求数据延迟问题排查
问题解决:Fetch请求结果延迟渲染导致表格空白行
问题描述
尝试将Fetch请求返回的数据添加到表格新行,但第一次点击按钮时会插入空白行,目标数据需等到下一次点击才会显示。该问题还导致total()函数报错,因为首个单元格值为undefined。直接添加字符串时新行可正常生成,推测问题与变量或Fetch请求特性相关,尝试修改数据类型、调整表格插入位置、修正语法均无效。
原代码
JavaScript代码
document.getElementById('getFood').addEventListener('click', getFood); document.getElementById('getFood').addEventListener('click', total); /*function getText() { $.ajax({ method: 'GET', url: 'https://api.api-ninjas.com/v1/nutrition?query=' + document.getElementById('foodInput'), headers: { 'X-Api-Key': 'vrtwcc/pVgAr2o/a4dEyYA==hR1m7lLVdU4ho4hW' }, contentType: 'application/json', success: function (result) { console.log(result); }, error: function ajaxError(jqXHR) { console.error('Error: ', jqXHR.responseText); }, }); }*/ /*function getFood(foodName) { let xhr = new XMLHttpRequest(); xhr.open( 'get', 'https://api.api-ninjas.com/v1/nutrition?query=' + foodName, true ); xhr.send(); xhr.addEventListener('load', function () { console.log(xhr.responseText); }); } getFood('fries');*/ function getFood() { fetch( 'https://api.api-ninjas.com/v1/nutrition?query=' + `${document.getElementById('foodInput').value}`, { method: 'GET', headers: { 'X-Api-Key': 'vrtwcc/pVgAr2o/a4dEyYA==hR1m7lLVdU4ho4hW' }, contentType: 'application/json', } ) .then((res) => res.json()) .then((data) => { foodQuery = data[0].name; calorieQuery = `${data[0].calories} calories`; }); let table = document .getElementById('foodTable') .getElementsByTagName('tbody')[0]; let row = table.insertRow(0); var cell1 = row.insertCell(0); var cell2 = row.insertCell(1); cell1.innerHTML = foodQuery; cell2.innerHTML = calorieQuery; // row.insertCell(0).innerHTML = foodQuery; // row.insertCell(1).innerHTML = calorieQuery; //document.getElementById('foodInput').value = ''; } function total() { let table = document.getElementById('foodTable'); let total = 0; for (let i = 1; i < table.rows.length; i++) { total += Number(table.rows[i].cells[2].innerText); } document.getElementById('tableTotal').value = total; }
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script src="https://code.jquery.com/jquery-3.6.3.js" integrity="sha256-nQLuAZGRRcILA+6dMBOvcRh5Pe310sBpanc6+QBmyVM=" crossorigin="anonymous" ></script> <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css" /> <link rel="stylesheet" href="style.css" /> <title>Document</title> </head> <body> <div class="container-fluid border border-primary"> <strong>Jason's Calorie Counter </strong><br /> <form> <div class="form-group" id=""> <label>Food Selection</label> <input class="form-control" id="foodInput" placeholder="Enter food here" /> <small id="foodHelp" class="form-text text-muted" >Nutrition data for each food item is scaled to 100g unless a quantity is specified </small> <br /> <button type="button" class="btn btn-primary" id="getFood"> Get Food </button> </div> </form> </div> <table class="table table-bordered" id="foodTable"> <thead> <tr> <th scope="col">Food</th> <th scope="col">Calories</th> </tr> </thead> <tbody> <tr id="rows1"> <td colspan="2" id="tableTotal">Total</td> </tr> </tbody> </table> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.7/dist/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous" ></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous" ></script> <script src="script.js"></script> </body> </html>
问题根源
- 异步操作顺序错误:Fetch是异步操作,原代码中表格插入逻辑写在
then回调之外,浏览器会先执行插入行的代码,此时foodQuery和calorieQuery还未被API数据赋值(甚至是未定义的全局变量),因此插入空白行;API请求完成后变量才被赋值,下次点击时才会用上本次数据。 - 总计函数逻辑错误:表格仅两列,但代码尝试访问
cells[2](索引从0开始,对应第三列),会获取到undefined导致转换数字报错;同时遍历行时包含了总计行,计算逻辑混乱。
修正后的代码
JavaScript代码
document.getElementById('getFood').addEventListener('click', handleFoodSubmit); // 合并逻辑,确保先获取数据再更新表格和计算总计 function handleFoodSubmit() { const foodInput = document.getElementById('foodInput').value.trim(); if (!foodInput) return; // 空输入直接返回 fetch( `https://api.api-ninjas.com/v1/nutrition?query=${foodInput}`, { method: 'GET', headers: { 'X-Api-Key': 'vrtwcc/pVgAr2o/a4dEyYA==hR1m7lLVdU4ho4hW' }, } ) .then((res) => { if (!res.ok) throw new Error('请求失败'); return res.json(); }) .then((data) => { if (!data.length) throw new Error('未找到对应食物数据'); const foodName = data[0].name; const calories = data[0].calories; // 插入表格行(放在then里,确保拿到数据后再操作) const tableBody = document.getElementById('foodTable').querySelector('tbody'); // 插入到总计行之前 const row = tableBody.insertRow(tableBody.rows.length - 1); const cell1 = row.insertCell(0); const cell2 = row.insertCell(1); cell1.textContent = foodName; cell2.textContent = `${calories} calories`; // 清空输入框 document.getElementById('foodInput').value = ''; // 计算总计 calculateTotal(); }) .catch((err) => { console.error(err.message); alert(err.message); }); } function calculateTotal() { const tableBody = document.getElementById('foodTable').querySelector('tbody'); let total = 0; // 遍历除了最后一行(总计行)的所有行 for (let i = 0; i < tableBody.rows.length - 1; i++) { const calorieText = tableBody.rows[i].cells[1].textContent; // 提取数字部分 const calories = parseInt(calorieText.split(' ')[0], 10); if (!isNaN(calories)) { total += calories; } } // 更新总计行内容 document.getElementById('tableTotal').textContent = `Total: ${total} calories`; }
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script src="https://code.jquery.com/jquery-3.6.3.js" integrity="sha256-nQLuAZGRRcILA+6dMBOvcRh5Pe310sBpanc6+QBmyVM=" crossorigin="anonymous"></script> <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css" /> <link rel="stylesheet" href="style.css" /> <title>Calorie Counter</title> </head> <body> <div class="container-fluid border border-primary"> <strong>Jason's Calorie Counter </strong><br /> <form> <div class="form-group"> <label>Food Selection</label> <input class="form-control" id="foodInput" placeholder="Enter food here" /> <small id="foodHelp" class="form-text text-muted"> Nutrition data for each food item is scaled to 100g unless a quantity is specified </small> <br /> <button type="button" class="btn btn-primary" id="getFood">Get Food</button> </div> </form> </div> <table class="table table-bordered" id="foodTable"> <thead> <tr> <th scope="col">Food</th> <th scope="col">Calories</th> </tr> </thead> <tbody> <tr> <td colspan="2" id="tableTotal">Total: 0 calories</td> </tr> </tbody> </table> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.7/dist/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> <script src="script.js"></script> </body> </html>
关键修改点
- 将表格插入逻辑移到Fetch的
then回调内部,确保拿到API数据后再更新表格 - 合并按钮点击事件,按"获取数据→更新表格→计算总计"的顺序执行,避免异步顺序问题
- 修复
calculateTotal函数的列索引错误,改为访问cells[1](卡路里列),并提取数字部分计算 - 遍历表格行时排除总计行,避免重复计算
- 添加输入验证和错误处理,防止空输入或API请求失败的情况
- 使用
textContent替代innerHTML,提升安全性
内容的提问来源于stack exchange,提问作者beebs9000
相关产品推荐
相关产品推荐

