You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 04:15:27