【问题标题】:How to properly loop through HTTP requests using XMLHttpRequest()?如何使用 XMLHttpRequest() 正确循环 HTTP 请求?
【发布时间】:2019-04-19 10:48:24
【问题描述】:

在进入任何主要框架之前,我目前正在学习 vanilla JS,以便掌握 HTTP 请求机制。我确信有很多更好的 JS 库可用于 HTTP 请求,但我想弄清楚如何使用经典的 XMLHttpRequest() 解决我的问题,因此请不要建议任何不包含 XMLHttpRequest() 的解决方案。

我正在尝试遍历我拥有的数组,并使用数组中的信息向 API 发出 HTTP GET 请求,然后用响应数据填充我的 HTML。代码很简单:

接收我的数字数组的函数:

function loadUniqueProjs(uniqueArray)
{

    var reqObject = [];

    for (var i in unique_array) 
    {
         outsideLoopRequest(unique_array[i], reqObject, i);
    }


}

我遍历数组并执行一个应该执行我的 GET 请求的函数:

function outsideLoopRequest(arrayValue,reqObject, i){

    // Create XHR Object
    reqObject[i] = new XMLHttpRequest();
    reqObject[i].open('GET', 'http://localhost:3000/resource/Projects/' + arrayValue, true)

    reqObject[i].onload = function() {
        if (this.status == 200) {
            var client = JSON.parse(this.responseText);
            var output = '';

            for (var j in client){

                output +=   '<div class="card">'+
                                    '<h5 class="card-header" role="tab" id="heading'+ j + '">'+
                                        '<a data-toggle="collapse" data-parent="#accordion" style="color:black"' + 
                                            'href="#collapse' + j + '"aria-expanded="false" aria-controls="collapse' + j + '"class="d-block collapsed">' +
                                            '<i class="fa fa-angle-double-down pull-right"></i>#' +
                                            client[j].projectTitle + ' | ' + 'PM: ' + client[j].projectManager + ' | ' +
                                            'PO: ' + client[j].projectOwner + ' | ' + 'Estimated Deadline: ' + client[j].predictedCompletion +
                                            ' | ' + 'Status: ' + client[j].status +
                                            ' | ' + 'Requestor: ' + client[j].requestor +
                                        '</a>'+
                                    '</h5>'+
                            '</div>';
            }
    }

    document.getElementById("spinner").hidden = true;
    // output the data into the HTML page
    document.getElementById('accordion').innerHTML = output;

    console.log(this.status + ' Data retrieved successfully!');

}
    reqObject[i].send();
}

经过数小时的逐步调试,我了解到 HTTP 请求是异步的,当利用循环逐个执行请求时,它们的行为不会像您希望的那样。请求不会一个一个地执行添加我需要的 HTML,而是循环首先打开所有请求,然后在它们到来时执行,并且当在 web 调试器中单步执行代码时,代码会在整个地方跳跃,它变得非常混乱(对不起,咆哮)。

我希望它一步一步地表现。我对 SO 进行了研究,有人建议这些是范围问题,并且请求应该在一个单独的函数中进行,这就是为什么我在一个函数中使用循环构建我的代码,在另一个函数中执行请求,但它仍然表现不佳前面描述过。请问有谁可以分享一下他们的专业知识吗?

【问题讨论】:

    标签: javascript ajax xmlhttprequest


    【解决方案1】:
    1. XMLHttpRequest 完成后做某事的唯一方法是将那个“某事”作为回调传递。对于请求链,您必须递归地执行此操作,因此第一个请求必须接收所有要执行的请求的有序列表,以及在最后一个请求完成后执行的完成回调。代码可能会变得丑陋,我不会尝试。

    2. 您仍然可以立即发送所有请求,并以正确的顺序呈现数据。从创建输出数据的基本骨架结构开始

    var outputStructure = '';
    
    for (var i in unique_array) {
       var cardId = 'card-' + id;
       outputStructure += `<div class="card" id="card-${i}">`;
       outsideLoopRequest(unique_array[i], reqObject, i);
    }
    
    document.getElementById('accordion').innerHTML = outputStructure;
    

    完成后,将数据放入具有正确 ID 的卡片中。

        reqObject[i].onload = function() {
    // create the output structure, then
           document.getElementById(`card-${i}`).innerHTML = output;
    
    1. 此类问题是俗称“回调地狱”的一部分。使用 Promise 组织更复杂的异步操作(其中一些事情可以并行完成,其他事情必须等到前一个完成)变得容易得多。所有请求都完成后做些什么?一行:Promise.all(requests).then(//...

    使用fetch

    【讨论】:

    • 感谢您的解释。我认为@Andy 和@Takashi Harano 在代码中显示了您的建议 1 和 3,我稍后会研究。我实现了你的 2,唯一的问题是 outputStructure+= 中缺少 &lt;/div&gt; 结束标签,因此在卡片中制作卡片,output 仅显示 1 card
    【解决方案2】:

    这是您正在尝试的实现示例。
    每个请求都会一一同步执行。

    var index = 0;
    var uniqueArray = ['a', 'b', 'c'];
    var output = '';
    
    function http(arrayValue) {
      var url = 'http://localhost:3000/resource/Projects/' + arrayValue;
    
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (xhr.readyState == XMLHttpRequest.DONE) {
          onHttpDone(xhr);
        }
      };
    
      xhr.open('GET', url, true);
      xhr.send();
    }
    
    function onHttpDone(xhr) {
      if (xhr.status != 200) {
        console.log('HTTP ERROR: ' + xhr.status);
        return;
      }
    
      var client = JSON.parse(xhr.responseText);
    
      output += '<div class="card">' + client.someProperty + '</div>';
      document.getElementById('accordion').innerHTML = output;
    
      if (index < uniqueArray.length) {
        http(uniqueArray[index++]);
      }
    }
    
    http(uniqueArray[index++]);
    

    【讨论】:

      【解决方案3】:

      现代 JS 提供了许多处理异步操作的新方法。我们可以使用promises,而不是调用 API 并尝试在每次迭代中返回数据。

      1) 将一组 promise 传递给Promise.all

      这将使用 map 迭代您的数组,以生成使用 fetch 生成的新承诺数组(类似于 XMLHTTPRequest,但基于承诺)和基于该迭代中数组元素的端点。 (我使用template literal 创建端点字符串而不是字符串连接)。

      const promises = unique_array.map(el => {
        const endpoint = `http://localhost:3000/resource/Projects/${el}`;
        return fetch(endpoint);
      });
      

      2) 现在我们有了一个 promise 数组,我们只需要将其提供给 Promise.all,它会返回一个带有调用所有这些 API 端点的结果的 promise。然后,您可以使用该数据循环它以创建您的 HTML。

      Promise.all(promises).then(data => {
        // loop over the data to create the HTML
      });
      

      举个例子怎么样?

      // We use a dummy fetch function to mimic a call to an
      // API. Data is returned after two seconds. It is an object
      // containing a number, and the square of that number
      function dummyFetch(n) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve({ number: n, square: n * n });
          }, 2000);
        });
      }
      
      const arr = [1, 2, 3, 4];
      
      // We produce the array of promises
      const promises = arr.map(el => dummyFetch(el));
      
      // When the promises have been resolved/rejected...
      Promise.all(promises).then(data => {
      
        // ...we can iterate over the data and produce an
        // array of html
        const html = data.map(({ number, square }) => {
          return `
            <div>
              <span class="number">${number}</span>
              <span class="square">${square}</span>
            </div>
          `
        }).join('');
      
        // ...which we can then add to the page 
        document.body.insertAdjacentHTML('beforeend', html);
      });
      .number { color: red; }
      .square { color: blue; }

      希望对您有所帮助。

      【讨论】:

        猜你喜欢
        • 2022-07-05
        • 2019-05-22
        • 2018-03-23
        • 2020-05-09
        • 2016-01-16
        • 2011-10-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多