如何使用XMLHttpRequest()正确循环处理HTTP请求并按序渲染?
Hey there! I see you're struggling with getting sequential XMLHttpRequest calls working—let's break down what's going wrong and fix it step by step.
The Core Issue
Your current code fires off all XHR requests at once in the loop. Since they're asynchronous, responses can come back in any order (whichever the server sends first), and using innerHTML = output each time overwrites previous content instead of appending it. Splitting the logic into separate functions didn't fix this because you were still launching all requests in parallel, not waiting for one to finish before starting the next.
Solution 1: Classic Recursive Approach (Traditional Vanilla JS)
This method uses recursion to ensure each request only starts after the previous one completes. It's aligned with the classic XHR pattern you're working with:
function loadUniqueProjs(uniqueArray) { let currentIndex = 0; const accordionElement = document.getElementById('accordion'); const spinnerElement = document.getElementById("spinner"); // Recursive function to handle one request at a time function processNextRequest() { // Exit if we've processed all items if (currentIndex >= uniqueArray.length) { spinnerElement.hidden = true; console.log("All requests completed successfully!"); return; } const arrayValue = uniqueArray[currentIndex]; const xhr = new XMLHttpRequest(); xhr.open('GET', `http://localhost:3000/resource/Projects/${arrayValue}`, true); xhr.onload = function() { if (this.status === 200) { const client = JSON.parse(this.responseText); let output = ''; // Build the HTML for this response for (const key in client){ output += `<div class="card">`+ `<h5 class="card-header" role="tab" id="heading${key}">`+ `<a data-toggle="collapse" data-parent="#accordion" style="color:black"` + `href="#collapse${key}" aria-expanded="false" aria-controls="collapse${key}" class="d-block collapsed">` + `<i class="fa fa-angle-double-down pull-right"></i>#${client[key].projectTitle} | ` + `PM: ${client[key].projectManager} | ` + `PO: ${client[key].projectOwner} | ` + `Estimated Deadline: ${client[key].predictedCompletion} | ` + `Status: ${client[key].status} | ` + `Requestor: ${client[key].requestor}</a>`+ `</h5>`+ `</div>`; } // Append the new content instead of overwriting it accordionElement.innerHTML += output; console.log(`${this.status} Data retrieved successfully for item: ${arrayValue}`); } else { console.error(`Request failed for item ${arrayValue}, status code: ${this.status}`); } // Move to the next item in the array currentIndex++; processNextRequest(); }; // Handle network errors to avoid breaking the sequence xhr.onerror = function() { console.error(`Network error for item ${arrayValue}`); currentIndex++; processNextRequest(); }; xhr.send(); } // Start the sequential request chain processNextRequest(); }
How This Works:
- We use a recursive function
processNextRequestthat only runs one XHR call at a time. - After each request finishes (success or failure), we increment our index and call the function again to handle the next item.
- We use
innerHTML +=instead ofinnerHTML =to append new content, preserving the order of your array.
Solution 2: Promise + Async/Await (Modern Vanilla JS)
If you're open to using ES6+ features (still vanilla JS!), wrapping XHR in a Promise and using async/await makes the code cleaner and easier to read:
// Wrap XHR in a Promise for async/await compatibility function makeXHRRequest(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onload = function() { if (this.status === 200) { resolve(JSON.parse(this.responseText)); } else { reject(new Error(`Request failed with status: ${this.status}`)); } }; xhr.onerror = function() { reject(new Error('Network error occurred')); }; xhr.send(); }); } async function loadUniqueProjs(uniqueArray) { const accordionElement = document.getElementById('accordion'); const spinnerElement = document.getElementById("spinner"); try { for (const arrayValue of uniqueArray) { const url = `http://localhost:3000/resource/Projects/${arrayValue}`; const client = await makeXHRRequest(url); let output = ''; for (const key in client){ output += `<div class="card">`+ `<h5 class="card-header" role="tab" id="heading${key}">`+ `<a data-toggle="collapse" data-parent="#accordion" style="color:black"` + `href="#collapse${key}" aria-expanded="false" aria-controls="collapse${key}" class="d-block collapsed">` + `<i class="fa fa-angle-double-down pull-right"></i>#${client[key].projectTitle} | ` + `PM: ${client[key].projectManager} | ` + `PO: ${client[key].projectOwner} | ` + `Estimated Deadline: ${client[key].predictedCompletion} | ` + `Status: ${client[key].status} | ` + `Requestor: ${client[key].requestor}</a>`+ `</h5>`+ `</div>`; } accordionElement.innerHTML += output; console.log(`Data retrieved successfully for item: ${arrayValue}`); } } catch (error) { console.error('Error during requests:', error); } finally { spinnerElement.hidden = true; console.log("All requests processed!"); } }
How This Works:
makeXHRRequestconverts the callback-based XHR into a Promise, which lets us useawait.- The
asyncfunctionloadUniqueProjsuses afor...ofloop withawaitto pause execution until each request finishes before moving to the next.
Quick Notes on Your Original Code
- Avoid using
for...into iterate over arrays (it can include unexpected enumerable properties). Useforloops,for...of, or array methods likeforEachinstead. - Always handle error cases (network failures, non-200 status codes) to prevent one failed request from breaking the entire sequence.
内容的提问来源于stack exchange,提问作者asleniovas

