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

如何根据数据量批量创建XMLHttpRequest并正确处理回调?

问题

我有一段JavaScript代码,通过XMLHttpRequest(简称xhr)发起请求,收到响应后会通过第二个XMLHttpRequest(xhr2)获取剩余数据。现在需要将第二次请求拆分为批量调用,根据数据量决定调用次数。首次请求后可以计算出需要调用的次数:

var batches = Math.ceil((counter.innerText.substring(0,counter.innerText.indexOf(" ")) - 100) / 1000);

但直接复用xhr2多次调用无法正常工作,因为第一次调用后它会关闭。我意识到需要根据batches的值初始化多个XMLHttpRequest,但不知道如何简洁地定义onreadystatechange函数。

最初的代码如下:

function get_tracklist_data(path, cid, title)
{
    var xhr = new XMLHttpRequest();
    var xhr2 = new XMLHttpRequest();
    xhr.onreadystatechange = function()
    {
        if (this.readyState == 4 && this.status == 200)
        {
            var counter = document.getElementById("counter");
            counter.innerHTML = xhr.responseText.substring(0, xhr.responseText.indexOf(":"));

            var data    = document.getElementById("data");
            data.innerHTML = xhr.responseText.substring(xhr.responseText.indexOf(":") + 1);
    
            //计算需要调用的次数
            var batches = Math.ceil((counter.innerText.substring(0,counter.innerText.indexOf(" ")) - 100) / 1000);
            for(i=1; i<batches;i++)
            {
                xhr2.open('GET',path + '?cid=' + cid + "&title=" + title+"&batch=" + i, true);
                xhr2.send();
            }
        }
    };

    xhr2.onreadystatechange = function()
        {
            if (this.readyState == 4 && this.status == 200)
            {
                var data    = document.getElementById("tbody");
                data.innerHTML+=xhr2.responseText;
            }
        };
    xhr.open('GET',path + '?cid=' + cid + "&title=" + title +"&batch=0", true);
    xhr.send();
};

我尝试将xhr2改为循环内的局部变量,但仍无法正常工作,会多次返回相同数据:

function get_tracklist_data(path, cid, title)
{
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function()
    {
        if (this.readyState == 4 && this.status == 200)
        {
            var counter = document.getElementById("counter");
            counter.innerHTML = xhr.responseText.substring(0, xhr.responseText.indexOf(":"));

            var data    = document.getElementById("data");
            data.innerHTML = xhr.responseText.substring(xhr.responseText.indexOf(":") + 1);

            //计算需要调用的次数
            var batches = Math.ceil((counter.innerText.substring(0,counter.innerText.indexOf(" ")) - 100) / 1000);
            for(i=1; i<batches;i++)
            {
                var xhr2 = new XMLHttpRequest();
                xhr2.onreadystatechange = function()
                    {
                        if (this.readyState == 4 && this.status == 200)
                        {
                            var data    = document.getElementById("tbody");
                            data.innerHTML+=xhr2.responseText;
                        }
                    };
                xhr2.open('GET',path + '?cid=' + cid + "&title=" + title+"&batch=" + i, true);
                xhr2.send();
            }
        }
    };


    xhr.open('GET',path + '?cid=' + cid + "&title=" + title +"&batch=0", true);
    xhr.send();
};

请问如何正确实现批量XMLHttpRequest调用并处理回调,避免重复数据问题?


解决方案

问题根源

重复数据问题的核心是闭包变量捕获和变量作用域的问题:

  1. 循环中使用var i声明变量,i属于函数级作用域,所有回调函数共享同一个i引用,当回调执行时,循环已经结束,i的值变成了batches,导致所有请求的batch参数都是同一个值。
  2. 回调里直接引用xhr2,但xhr2是循环内的变量,当回调异步执行时,xhr2已经被循环覆盖为最后一次创建的实例,导致所有回调都用同一个xhr2的响应数据。

修复方案

方案1:使用ES6的let声明变量(推荐)

let会创建块级作用域,每次循环都会生成独立的i和xhr2实例,回调会捕获当前循环的变量:

function get_tracklist_data(path, cid, title)
{
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function()
    {
        if (this.readyState == 4 && this.status == 200)
        {
            var counter = document.getElementById("counter");
            counter.innerHTML = xhr.responseText.substring(0, xhr.responseText.indexOf(":"));

            var data = document.getElementById("data");
            data.innerHTML = xhr.responseText.substring(xhr.responseText.indexOf(":") + 1);

            //计算需要调用的次数
            var batches = Math.ceil((counter.innerText.substring(0,counter.innerText.indexOf(" ")) - 100) / 1000);
            //用let声明i,创建块级作用域
            for(let i=1; i<batches;i++)
            {
                let xhr2 = new XMLHttpRequest();
                xhr2.onreadystatechange = function()
                {
                    if (this.readyState == 4 && this.status == 200)
                    {
                        var tbody = document.getElementById("tbody");
                        //用this指代当前的xhr实例,避免引用覆盖问题
                        tbody.innerHTML += this.responseText;
                    }
                };
                xhr2.open('GET', `${path}?cid=${cid}&title=${title}&batch=${i}`, true);
                xhr2.send();
            }
        }
    };

    xhr.open('GET', `${path}?cid=${cid}&title=${title}&batch=0`, true);
    xhr.send();
};

方案2:使用立即执行函数(IIFE)创建闭包(兼容旧浏览器)

如果需要兼容不支持ES6的环境,可以用IIFE把每次循环的i和xhr2封装到独立作用域中:

function get_tracklist_data(path, cid, title)
{
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function()
    {
        if (this.readyState == 4 && this.status == 200)
        {
            var counter = document.getElementById("counter");
            counter.innerHTML = xhr.responseText.substring(0, xhr.responseText.indexOf(":"));

            var data = document.getElementById("data");
            data.innerHTML = xhr.responseText.substring(xhr.responseText.indexOf(":") + 1);

            //计算需要调用的次数
            var batches = Math.ceil((counter.innerText.substring(0,counter.innerText.indexOf(" ")) - 100) / 1000);
            for(var i=1; i<batches;i++)
            {
                //用IIFE封装当前循环的i和xhr2
                (function(batchNum){
                    var xhr2 = new XMLHttpRequest();
                    xhr2.onreadystatechange = function()
                    {
                        if (this.readyState == 4 && this.status == 200)
                        {
                            var tbody = document.getElementById("tbody");
                            tbody.innerHTML += this.responseText;
                        }
                    };
                    xhr2.open('GET', path + '?cid=' + cid + "&title=" + title + "&batch=" + batchNum, true);
                    xhr2.send();
                })(i);
            }
        }
    };

    xhr.open('GET', path + '?cid=' + cid + "&title=" + title + "&batch=0", true);
    xhr.send();
};

额外优化建议

  • 回调中使用this.responseText代替xhr2.responseText,this在onreadystatechange回调中就是当前的XMLHttpRequest实例,更安全。
  • 可以用模板字符串拼接URL,让代码更易读(如方案1中的${path}?cid=${cid}&title=${title}&batch=${i})。
  • 如果需要保证请求顺序,可以把批量请求改成串行执行,但如果顺序不敏感,并行请求效率更高。

内容的提问来源于stack exchange,提问作者Paul Taylor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 20:40:49