如何在循环中正确执行XMLHttpRequest,实现串行请求
实现XHR请求串行执行的几种方案
这是个很常见的异步串行执行问题——因为map()是同步遍历数组,会一次性触发所有XHR请求,而我们需要让每个请求等待前一个完成后再启动。下面给你几种实用的解决方案,都是基于Promise来实现串行控制的,因为Promise能很好地处理异步操作的顺序:
第一步:把XHR封装成Promise
首先,我们需要将原生的XHR调用包装成返回Promise的函数,这样才能方便地用Promise的链式能力或者async/await来控制顺序:
function makeXHRRequest(url, method = 'GET', data = null) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open(method, url); // 如果是POST等带数据的请求,设置对应的请求头(根据你的数据格式调整) if (data) { xhr.setRequestHeader('Content-Type', 'application/json'); } xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { // 请求成功,返回响应内容(如果是JSON可以解析后返回) resolve(JSON.parse(xhr.responseText) || xhr.responseText); } else { reject(new Error(`请求失败,状态码:${xhr.status}`)); } }; xhr.onerror = () => { reject(new Error('网络请求出错')); }; // 发送请求,POST的话传入数据(这里假设是JSON格式,需要转成字符串) xhr.send(data ? JSON.stringify(data) : null); }); }
方案一:用async/await + for...of循环(最直观易读)
async/await可以让异步代码看起来像同步代码,在for...of循环里使用时,会自动等待每个请求完成后再进入下一次循环:
async function runSerialRequests(urlList) { const responseResults = []; for (const url of urlList) { try { console.log(`开始请求:${url}`); const response = await makeXHRRequest(url); responseResults.push(response); console.log(`请求完成:${url}`); } catch (error) { console.error(`请求失败:${url}`, error); // 这里可以选择继续执行下一个请求,或者抛出错误终止整个流程 // 如果要终止,直接写 throw error; } } return responseResults; } // 调用示例 const targetUrls = ['/api/data1', '/api/data2', '/api/data3']; runSerialRequests(targetUrls) .then(allResults => console.log('所有请求完成:', allResults)) .catch(totalError => console.error('流程终止:', totalError));
方案二:递归遍历(无需依赖async/await,兼容旧环境)
如果你的运行环境不支持async/await,可以用递归函数手动控制请求顺序:
function runSerialRecursive(urlList, currentIndex = 0, results = []) { // 终止条件:所有请求都处理完了 if (currentIndex >= urlList.length) { return Promise.resolve(results); } const currentUrl = urlList[currentIndex]; console.log(`开始请求:${currentUrl}`); return makeXHRRequest(currentUrl) .then(response => { results.push(response); console.log(`请求完成:${currentUrl}`); // 递归调用,处理下一个请求 return runSerialRecursive(urlList, currentIndex + 1, results); }) .catch(error => { console.error(`请求失败:${currentUrl}`, error); // 选择继续执行的话,直接递归下一个;要终止就返回Promise.reject(error) return runSerialRecursive(urlList, currentIndex + 1, results); }); } // 调用示例 runSerialRecursive(targetUrls) .then(allResults => console.log('所有请求完成:', allResults));
方案三:用Array.reduce构建Promise链(函数式写法)
利用数组的reduce方法,逐步构建一个Promise链式调用,每个Promise都等待前一个完成后再执行:
function runSerialWithReduce(urlList) { return urlList.reduce((promiseChain, currentUrl) => { return promiseChain.then(prevResults => { console.log(`开始请求:${currentUrl}`); return makeXHRRequest(currentUrl) .then(response => { console.log(`请求完成:${currentUrl}`); // 将新结果加入到之前的结果数组中 return [...prevResults, response]; }) .catch(error => { console.error(`请求失败:${currentUrl}`, error); // 继续执行的话返回之前的结果数组;终止就返回Promise.reject(error) return prevResults; }); }); }, Promise.resolve([])); // 初始Promise,返回空数组 } // 调用示例 runSerialWithReduce(targetUrls) .then(allResults => console.log('所有请求完成:', allResults));
关键原理说明
原来的map()方法会同步遍历数组,立刻执行每个回调里的xhr.send(),所以所有请求会同时触发。而上面的方案都是通过让每个请求的执行依赖前一个请求的完成事件,从而实现串行执行的效果。你可以根据自己的代码风格和运行环境选择合适的方案~
内容的提问来源于stack exchange,提问作者1110
相关产品推荐
相关产品推荐

