setupAndStart函数中categories数组无法打印内容的原因排查
问题:遍历categories数组无输出的原因及解决方法
我实现了setupAndStart()函数,逻辑是先调用getCategoryIds()获取分类ID数组,再遍历数组将每个ID传入getCategory(id)获取分类对象,最后把对象推入categories数组。但当我尝试遍历categories数组并打印元素时,没有任何输出。相关代码如下:
const URL = `https://jservice.io/api`; const NUM_QUESTIONS = 5; const NUM_CATEGORIES = 6; // categories is the main data structure for the app; it looks like this: // [ // { title: "Math", // clues: [ // {question: "2+2", answer: 4, showing: null}, // {question: "1+1", answer: 2, showing: null} // ... // ], // }, // { title: "Literature", // clues: [ // {question: "Hamlet Author", answer: "Shakespeare", showing: null}, // {question: "Bell Jar Author", answer: "Plath", showing: null}, // ... // ], // }, // ... // ] let categories = []; /** Get NUM_CATEGORIES random category from API. * * Returns array of category ids */ async function getCategoryIds() { let response = await axios.get(`${URL}/categories`, { params: { count: "100", offset: 20 }, }); let randomCategory = _.sampleSize(response.data, NUM_CATEGORIES); let categoryIDs = randomCategory.map((item) => { return item.id; }); return categoryIDs; } /** Return object with data about a category: * * Returns { title: "Math", clues: clue-array } * * Where clue-array is: * [ * {question: "Hamlet Author", answer: "Shakespeare", showing: null}, * {question: "Bell Jar Author", answer: "Plath", showing: null}, * ... * ] */ async function getCategory(catId) { let response = await axios.get(`${URL}/clues`, { params: { category: catId }, }); let fiveClues = _.sampleSize(response.data, NUM_QUESTIONS); let questionAnswerArray = fiveClues.map((arr) => { return { question: arr.question, answer: arr.answer, showing: null, }; }); let titleClueObj = { title: fiveClues[0].category.title, clues: questionAnswerArray, }; return titleClueObj; } /** Fill the HTML table#jeopardy with the categories & cells for questions. * * - The <thead> should be filled w/a <tr>, and a <td> for each category * - The <tbody> should be filled w/NUM_QUESTIONS_PER_CAT <tr>s, * each with a question for each category in a <td> * (initally, just show a "?" where the question/answer would go.) */ async function fillTable() { let catagoryRow = $("<tr>"); $("thead").append(catagoryRow); for (let i = 0; i < NUM_CATEGORIES; i++) { $("<td>Cat</td>").appendTo(catagoryRow); } for (let y = 0; y < NUM_QUESTIONS; y++) { let questionRow = $("<tr>"); $("tbody").append(questionRow); for (let x = 0; x < NUM_CATEGORIES; x++) { let question = $("<td>").text("?").attr("id", `${x}-${y}`); question.appendTo(questionRow); } } } /** Handle clicking on a clue: show the question or answer. * * Uses .showing property on clue to determine what to show: * - if currently null, show question & set .showing to "question" * - if currently "question", show answer & set .showing to "answer" * - if currently "answer", ignore click * */ function handleClick(evt) {} /** Wipe the current Jeopardy board, show the loading spinner, * and update the button used to fetch data. */ function showLoadingView() { $("table").show(); $(".start").show(); } /** Remove the loading spinner and update the button used to fetch data. */ function hideLoadingView() { $("header").hide(); $("#spin-container").hide(); } /** Start game: * * - get random category Ids * - get data for each category * - create HTML table * */ async function setupAndStart() { let categoryIDs = await getCategoryIds(); categoryIDs.forEach(async (id) => { let tempCategory = await getCategory(id); categories.push(tempCategory); }); fillTable(); for (let i = 0; i < categories.length; i++) { console.log(categories); } } /** On click of start / restart button, set up game. */ // TODO /** On page load, add event handler for clicking clues */ // TODO setupAndStart();
问题原因
核心问题出在forEach循环结合async/await的使用上:
forEach不会等待异步回调函数执行完成,当你调用categoryIDs.forEach(async (id) => {...})时,循环会立即结束,不会等待所有getCategory(id)的异步请求完成。- 因此,在执行
fillTable()和打印categories时,所有的异步请求还在处理中,categories数组仍然是空的,自然没有输出。
解决方法
方法1:使用for...of循环(顺序执行异步请求)
for...of会等待每个异步操作完成后再进行下一次循环,确保categories数组被正确填充后再执行后续代码:
async function setupAndStart() { let categoryIDs = await getCategoryIds(); // 替换forEach为for...of for (const id of categoryIDs) { let tempCategory = await getCategory(id); categories.push(tempCategory); } fillTable(); // 现在可以正确打印categories内容 console.log(categories); }
方法2:使用Promise.all(并行执行异步请求,效率更高)
如果不需要严格顺序执行,推荐用Promise.all批量处理所有异步请求,同时等待所有请求完成,性能更优:
async function setupAndStart() { let categoryIDs = await getCategoryIds(); // 生成所有getCategory的Promise数组 const categoryPromises = categoryIDs.map(id => getCategory(id)); // 等待所有Promise完成,直接得到填充好的categories数组 categories = await Promise.all(categoryPromises); fillTable(); console.log(categories); }
额外优化建议
- 你的
fillTable()函数目前只是固定显示"Cat",后续可以修改为使用categories数组中的真实分类标题,比如:
async function fillTable() { let catagoryRow = $("<tr>"); $("thead").append(catagoryRow); // 遍历categories显示真实标题 for (const category of categories) { $("<td>").text(category.title).appendTo(catagoryRow); } // 剩余代码保持不变... }
内容的提问来源于stack exchange,提问作者strykerr21
相关产品推荐
相关产品推荐

