You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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是否会导致竞态条件或不可靠行为。

想了解的内容

  1. 如何让该函数更可靠?
  2. 是否有更现代的处理方式(如使用fetch结合async/await)?
  3. 管理嵌套异步请求的推荐方案是什么?

解决方案

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扁平化代码;
  • 明确请求之间的依赖关系:有依赖则串行,无依赖则并行;
  • 统一错误处理,避免遗漏异常导致的
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 02:02:52