使用jQuery的$.ajax/getJSON按原数组顺序获取Twitch频道数据问题
我来帮你一步步解决这三个问题,先从根源说起,再给你具体的代码方案:
问题2:为什么console.log(channels)能看到数据但channels.length为0?
这是异步操作的时序问题。你第二个$.ajax用了async:'true'(注意这里应该写布尔值true,不是字符串),意味着浏览器发送请求后会直接执行后续代码,不会等待请求完成。当你执行console.log(channels)和console.log(channels.length)时,所有异步请求还没返回数据,channels数组还是空的,所以length显示为0。而浏览器控制台有个特性:它显示的数组是实时引用,当后续请求完成把数据push进去后,你展开控制台里的数组就能看到最新数据,但length是执行console.log时的数值,不会更新。
问题1:数组顺序与原user_arr不一致
同样是异步请求导致的:每个请求的响应返回时间不同,谁先拿到数据谁就先被push到数组里,自然和原数组顺序对不上。你第一个请求用了async:false(同步),但第二个是异步,这会导致逻辑混乱,而且async:false已经被废弃,强烈不推荐使用。
解决方案:用jQuery的Deferred(类Promise)保证顺序并处理异步
jQuery的$.ajax返回的是Deferred对象,它和Promise类似,能帮我们优雅处理异步操作。下面给你两种实用方案:
方案1:串行请求(严格按原数组顺序,一个请求完成再发下一个)
适合需要严格顺序的场景,缺点是总耗时为所有请求时间之和:
$(document).ready(function(){ const user_arr = ["ESL_SC2", "OgamingSC2", "cretetion", "freecodecamp", "storbeck", "habathcx", "RobotCaleb", "noobs2ninjas"]; const resultArr = []; // 存储每个用户的合并数据 // 递归函数,逐个处理用户 function processUser(index) { if (index >= user_arr.length) { // 所有用户处理完成 console.log("最终结果数组:", resultArr); console.log("数组长度:", resultArr.length); return; } const username = user_arr[index]; const streamUrl = `https://wind-bow.gomix.me/twitch-api/streams/${username}?callback=?`; const channelUrl = `https://wind-bow.gomix.me/twitch-api/channels/${username}?callback=?`; // 同时获取当前用户的stream和channel数据 $.when( $.getJSON(streamUrl), $.getJSON(channelUrl) ).done(function(streamData, channelData) { // 合并当前用户的两个数据 resultArr.push({ username: username, stream: streamData[0], // $.getJSON的done回调参数是数组,第一个元素为返回数据 channel: channelData[0] }); // 处理下一个用户 processUser(index + 1); }).fail(function(err) { console.error(`处理用户${username}时出错:`, err); processUser(index + 1); // 出错也继续处理下一个用户 }); } // 开始处理第一个用户 processUser(0); });
方案2:并行请求(所有请求同时发送,最后按原顺序整理)
效率更高,总耗时为最慢请求的时间,我们给每个请求标记原数组索引,最后按索引排序:
$(document).ready(function(){ const user_arr = ["ESL_SC2", "OgamingSC2", "cretetion", "freecodecamp", "storbeck", "habathcx", "RobotCaleb", "noobs2ninjas"]; const deferreds = []; // 为每个用户创建异步任务 user_arr.forEach((username, index) => { const streamUrl = `https://wind-bow.gomix.me/twitch-api/streams/${username}?callback=?`; const channelUrl = `https://wind-bow.gomix.me/twitch-api/channels/${username}?callback=?`; // 合并当前用户的两个请求 const userDeferred = $.when( $.getJSON(streamUrl), $.getJSON(channelUrl) ).then(function(streamData, channelData) { // 返回带索引的合并数据 return { index: index, username: username, stream: streamData[0], channel: channelData[0] }; }).fail(function(err) { console.error(`获取用户${username}数据出错:`, err); // 出错时也返回索引,保证顺序 return { index: index, username: username, error: err }; }); deferreds.push(userDeferred); }); // 等待所有请求完成 $.when.apply($, deferreds).done(function() { // 按索引排序,还原原数组顺序 const resultArr = Array.from(arguments).sort((a, b) => a.index - b.index); console.log("最终结果数组:", resultArr); console.log("数组长度:", resultArr.length); // 用map整理成自定义格式,比如只保留关键字段 const mappedArr = resultArr.map(item => ({ name: item.username, displayName: item.channel?.display_name || item.username, isStreaming: item.stream?.stream !== null })); console.log("map整理后的数组:", mappedArr); }); });
关于Promise的简单理解
你可以把Promise(或jQuery的Deferred)看成一个“异步任务容器”:它有等待中、成功、失败三种状态。当异步操作完成时,它会切换到对应状态,并执行预设的回调函数。
上面代码里的$.when()就是把多个异步任务合并成一个,当所有任务都成功时才执行done()里的逻辑;then()则用来处理单个任务的结果,把数据转换成我们需要的格式。这种写法完全替代了过时的async:false,完美解决顺序和异步时序问题。
内容的提问来源于stack exchange,提问作者Rajat Audichya

