fillTable函数不稳定如何优化?JSON填充HTML表格可靠性提升
问题描述
我用一个JavaScript函数从指定URL获取JSON数据填充HTML表格,这个函数有时正常工作,但偶尔会出现表格为空或数据显示不符合预期的情况。
函数代码
function fillTable(tablehead, tablebody, url) { return new Promise((resolve, reject) => { $(`#${tablehead}`).html(''); $(`#${tablebody}`).html(''); xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.send(); xhr.onreadystatechange = function() { var tabla = JSON.parse(xhr.responseText); if (xhr.readyState == 4 && xhr.status == 200) { if (url.search("/mozi") != -1) { xhr = new XMLHttpRequest(); xhr.open('GET', '/get_moziid_count', true); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { var moziidcount = JSON.parse(xhr.responseText); var fejlec = '<tr>'; for (var key in tabla[0]) { fejlec += `<th>` + key + '</th>'; } fejlec += '</tr>'; var sorok = ''; for (var i = 0; i < tabla.length; i++) { let szam = moziidcount.filter(m => m.moziid == tabla[i].id); sorok += `<tr title="Number of screenings: ${szam.length > 0 ? szam[0].count : 0}">` for (var k in tabla[i]) { sorok += '<td>' + tabla[i][k] + '</td>'; } sorok += '</tr>'; } $(`#${tablehead}`).html(fejlec); $(`#${tablebody}`).html(sorok); resolve(); } } } else { var fejlec = '<tr>'; for (var key in tabla[0]) { fejlec += `<th>` + key + '</th>'; } fejlec += '</tr>'; var sorok = ''; for (var i = 0; i < tabla.length; i++) { sorok += `<tr>`; for (var k in tabla[i]) { sorok += '<td>' + tabla[i][k] + '</td>'; } sorok += '</tr>'; } $(`#${tablehead}`).html(fejlec); $(`#${tablebody}`).html(sorok); resolve(); } } }; }); }
遇到的问题
- 表格偶尔为空;
- 在
readyState === 4之前调用tabla = JSON.parse(xhr.responseText),若响应未到达会抛出错误; - 内部的第二个
xhr.onreadystatechange可能覆盖第一个; - 不确定嵌套多个XMLHttpRequest是否会导致竞态条件或不可靠行为。
想了解的内容
- 如何让该函数更可靠?
- 是否有更现代的处理方式(如使用fetch结合async/await)?
- 管理嵌套异步请求的推荐方案是什么?
解决方案
1. 让函数更可靠的修正要点
针对代码中的问题,直接修改现有XHR代码即可提升可靠性:
- 延迟解析响应:仅当
readyState === 4且status === 200时,再执行JSON.parse,避免响应未完全返回或请求失败导致的解析错误; - 避免变量覆盖:给两个XHR实例分别命名(比如
mainXhr和countXhr),防止后续实例覆盖前一个,导致状态监听混乱; - 添加完整错误处理:请求失败、JSON解析出错时调用
reject抛出错误,避免Promise一直处于pending状态,同时给用户显示错误提示; - 处理空数据场景:如果返回的
tabla是空数组,生成空表头或提示行,避免表格完全空白。
修正后的XHR版本代码:
function fillTable(tablehead, tablebody, url) { return new Promise((resolve, reject) => { $(`#${tablehead}`).html(''); $(`#${tablebody}`).html(''); const mainXhr = new XMLHttpRequest(); mainXhr.open('GET', url, true); mainXhr.send(); mainXhr.onreadystatechange = function() { if (mainXhr.readyState !== 4) return; if (mainXhr.status === 200) { let tabla; try { tabla = JSON.parse(mainXhr.responseText); } catch (err) { reject(new Error('JSON解析失败: ' + err.message)); return; } if (url.includes("/mozi")) { const countXhr = new XMLHttpRequest(); countXhr.open('GET', '/get_moziid_count', true); countXhr.send(); countXhr.onreadystatechange = function() { if (countXhr.readyState !== 4) return; if (countXhr.status === 200) { let moziidcount; try { moziidcount = JSON.parse(countXhr.responseText); } catch (err) { reject(new Error('统计数据JSON解析失败: ' + err.message)); return; } let fejlec = '<tr>'; if (tabla.length > 0) { for (const key of Object.keys(tabla[0])) { fejlec += `<th>${key}</th>`; } } else { fejlec += `<th>无数据</th>`; } fejlec += '</tr>'; let sorok = ''; if (tabla.length === 0) { sorok += '<tr><td colspan="100%">暂无数据</td></tr>'; } else { for (const item of tabla) { const szam = moziidcount.find(m => m.moziid === item.id); sorok += `<tr title="Number of screenings: ${szam ? szam.count : 0}">`; for (const key of Object.keys(item)) { sorok += `<td>${item[key]}</td>`; } sorok += '</tr>'; } } $(`#${tablehead}`).html(fejlec); $(`#${tablebody}`).html(sorok); resolve(); } else { reject(new Error('获取统计数据失败: ' + countXhr.statusText)); } }; countXhr.onerror = () => reject(new Error('统计数据请求出错')); } else { let fejlec = '<tr>'; if (tabla.length > 0) { for (const key of Object.keys(tabla[0])) { fejlec += `<th>${key}</th>`; } } else { fejlec += `<th>无数据</th>`; } fejlec += '</tr>'; let sorok = ''; if (tabla.length === 0) { sorok += '<tr><td colspan="100%">暂无数据</td></tr>'; } else { for (const item of tabla) { sorok += `<tr>`; for (const key of Object.keys(item)) { sorok += `<td>${item[key]}</td>`; } sorok += '</tr>'; } } $(`#${tablehead}`).html(fejlec); $(`#${tablebody}`).html(sorok); resolve(); } } else { reject(new Error('请求数据失败: ' + mainXhr.statusText)); } }; mainXhr.onerror = () => reject(new Error('主数据请求出错')); }); }
2. 现代处理方式:fetch + async/await
用fetch替代XHR,结合async/await可以让异步代码更简洁易读,原生支持Promise,无需手动封装:
async function fillTable(tablehead, tablebody, url) { $(`#${tablehead}`).html(''); $(`#${tablebody}`).html(''); try { // 获取主数据 const mainRes = await fetch(url); if (!mainRes.ok) throw new Error(`主请求失败: ${mainRes.statusText}`); const tabla = await mainRes.json(); let fejlec = '<tr>'; let sorok = ''; if (url.includes("/mozi")) { // 获取统计数据 const countRes = await fetch('/get_moziid_count'); if (!countRes.ok) throw new Error(`统计请求失败: ${countRes.statusText}`); const moziidcount = await countRes.json(); // 生成表头 if (tabla.length > 0) { Object.keys(tabla[0]).forEach(key => fejlec += `<th>${key}</th>`); } else { fejlec += `<th>无数据</th>`; } fejlec += '</tr>'; // 生成表格行 if (tabla.length === 0) { sorok += '<tr><td colspan="100%">暂无数据</td></tr>'; } else { tabla.forEach(item => { const szam = moziidcount.find(m => m.moziid === item.id); sorok += `<tr title="Number of screenings: ${szam ? szam.count : 0}">`; Object.keys(item).forEach(key => sorok += `<td>${item[key]}</td>`); sorok += '</tr>'; }); } } else { // 生成表头 if (tabla.length > 0) { Object.keys(tabla[0]).forEach(key => fejlec += `<th>${key}</th>`); } else { fejlec += `<th>无数据</th>`; } fejlec += '</tr>'; // 生成表格行 if (tabla.length === 0) { sorok += '<tr><td colspan="100%">暂无数据</td></tr>'; } else { tabla.forEach(item => { sorok += `<tr>`; Object.keys(item).forEach(key => sorok += `<td>${item[key]}</td>`); sorok += '</tr>'; }); } } $(`#${tablehead}`).html(fejlec); $(`#${tablebody}`).html(sorok); } catch (err) { console.error('填充表格失败:', err); $(`#${tablebody}`).html('<tr><td colspan="100%">数据加载失败,请稍后重试</td></tr>'); throw err; // 保留错误抛出,方便外部处理 } }
这个版本的优势:
- 代码线性化,嵌套层级减少,可读性大幅提升;
fetch原生返回Promise,无需手动封装;async/await让异步代码看起来像同步代码,逻辑更清晰;- 错误处理集中在
try/catch块,更容易管理。
3. 嵌套异步请求的推荐管理方案
针对嵌套异步请求(比如先获取主数据,再获取关联数据),推荐以下几种方案:
方案1:串行await(适合依赖前序结果的场景)
如果第二个请求依赖第一个请求的结果,直接用await串行执行即可,逻辑简单直观,避免回调嵌套,就像上面的fetch版本那样。
方案2:Promise链式调用(不支持async/await时的替代方案)
如果环境不支持async/await,可以用Promise链式调用替代回调嵌套,避免"回调地狱":
function fillTable(tablehead, tablebody, url) { $(`#${tablehead}`).html(''); $(`#${tablebody}`).html(''); return fetch(url) .then(mainRes => { if (!mainRes.ok) throw new Error(`主请求失败: ${mainRes.statusText}`); return mainRes.json(); }) .then(tabla => { if (url.includes("/mozi")) { return fetch('/get_moziid_count') .then(countRes => { if (!countRes.ok) throw new Error(`统计请求失败: ${countRes.statusText}`); return countRes.json(); }) .then(moziidcount => ({ tabla, moziidcount })); } else { return { tabla }; } }) .then(({ tabla, moziidcount }) => { let fejlec = '<tr>'; let sorok = ''; if (tabla.length > 0) { Object.keys(tabla[0]).forEach(key => fejlec += `<th>${key}</th>`); } else { fejlec += `<th>无数据</th>`; } fejlec += '</tr>'; if (tabla.length === 0) { sorok += '<tr><td colspan="100%">暂无数据</td></tr>'; } else { tabla.forEach(item => { sorok += `<tr>`; if (moziidcount) { const szam = moziidcount.find(m => m.moziid === item.id); sorok = sorok.replace('<tr>', `<tr title="Number of screenings: ${szam ? szam.count : 0}">`); } Object.keys(item).forEach(key => sorok += `<td>${item[key]}</td>`); sorok += '</tr>'; }); } $(`#${tablehead}`).html(fejlec); $(`#${tablebody}`).html(sorok); }) .catch(err => { console.error('填充表格失败:', err); $(`#${tablebody}`).html('<tr><td colspan="100%">数据加载失败,请稍后重试</td></tr>'); throw err; }); }
方案3:并行请求(如果请求之间无依赖)
如果两个请求之间没有依赖关系,可以用Promise.all并行发起请求,提升加载速度:
async function fillTable(tablehead, tablebody, url) { $(`#${tablehead}`).html(''); $(`#${tablebody}`).html(''); try { const [mainRes, countRes] = await Promise.all([ fetch(url), url.includes("/mozi") ? fetch('/get_moziid_count') : Promise.resolve({ ok: true, json: () => [] }) ]); if (!mainRes.ok) throw new Error(`主请求失败: ${mainRes.statusText}`); const tabla = await mainRes.json(); let moziidcount = []; if (url.includes("/mozi")) { if (!countRes.ok) throw new Error(`统计请求失败: ${countRes.statusText}`); moziidcount = await countRes.json(); } // 后续生成表格的逻辑和之前一致 let fejlec = '<tr>'; let sorok = ''; if (tabla.length > 0) { Object.keys(tabla[0]).forEach(key => fejlec += `<th>${key}</th>`); } else { fejlec += `<th>无数据</th>`; } fejlec += '</tr>'; if (tabla.length === 0) { sorok += '<tr><td colspan="100%">暂无数据</td></tr>'; } else { tabla.forEach(item => { sorok += `<tr>`; if (moziidcount) { const szam = moziidcount.find(m => m.moziid === item.id); sorok = sorok.replace('<tr>', `<tr title="Number of screenings: ${szam ? szam.count : 0}">`); } Object.keys(item).forEach(key => sorok += `<td>${item[key]}</td>`); sorok += '</tr>'; }); } $(`#${tablehead}`).html(fejlec); $(`#${tablebody}`).html(sorok); } catch (err) { console.error('填充表格失败:', err); $(`#${tablebody}`).html('<tr><td colspan="100%">数据加载失败,请稍后重试</td></tr>'); throw err; } }
核心原则
- 避免回调嵌套(回调地狱),尽量用Promise链式调用或async/await扁平化代码;
- 明确请求之间的依赖关系:有依赖则串行,无依赖则并行;
- 统一错误处理,避免遗漏异常导致的
相关产品推荐
相关产品推荐

