如何使用XMLHttpRequest()正确遍历HTTP请求?

时间:2019-04-19 10:48:24

标签: javascript ajax xmlhttprequest

在进入任何主要框架之前,我目前正在学习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进行了研究,有人建议这些是范围问题,并且应该在单独的函数中提出请求,这就是为什么我在一个函数中使用循环构造代码,而在另一个函数中执行请求的原因,但仍然错误地表现为如前所述。有没有人可以分享他们的专业知识?

3 个答案:

答案 0 :(得分:1)

  1. XMLHttpRequest完成之后做某事的唯一方法是将“某事”作为回调传递。对于一个请求链,您必须递归执行,因此第一个请求必须接收所有要执行的请求的有序列表,再加上最后一个完成后要执行的finishing回调。代码可能很难看,我不会尝试。

  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.all(requests).then(//...

使用fetch

答案 1 :(得分:1)

这是您要尝试的实现示例。
每个请求将被同步一个接一个地执行。

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++]);

答案 2 :(得分:1)

现代JS提供了许多使用异步操作的新方法。我们可以使用promises来代替调用API并尝试每次迭代返回数据的方法。

1)做出一系列承诺以传递给Promise.all

这会使用map遍历您的数组,以生成一个新的由fetch生成的promise数组(类似于XMLHTTPRequest但基于promise),并基于该迭代中的array元素生成一个端点。 (我使用了template literal来创建端点字符串,而不是字符串串联)。

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

2)现在我们有了一个承诺数组,我们只需要将其提供给Promise.all即可,而 也会返回承诺并调用所有这些API端点。然后,您可以使用这些数据进行循环以创建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; }

希望有帮助。