【问题标题】:Javascript looping through array with a promises function returning values in random orderJavascript循环遍历数组并带有一个promise函数以随机顺序返回值
【发布时间】:2019-11-24 23:20:10
【问题描述】:

我的代码旨在循环遍历一组运动队 ID,以使用它们从 API 编译各种信息,然后将 HTML 添加到我的页面以显示每支球队最近完成的比赛。它正在工作并且看起来很棒,除了:每次刷新时结果都会以随机顺序出现在我的页面上。我认为这与循环快速移动到下一次迭代有关,而来自服务器的响应是随机返回的。我希望结果首先是“Patriots”,然后是“Steelers”等,但结果是随机的,Patriots 几乎不会排在第一位。

另外,我对 JS 很陌生,所以我确信我可以做很多事情来使它变得更好,所以提前谢谢你!

//Define the teams I want scores from and their API reference numbers.
let teams = new Map();
teams.set("Patriots", 134920);
teams.set("Steelers", 134925);
teams.set("Bruins", 134830);
teams.set("Penguins", 134844);
teams.set("Celtics", 134860);
teams.set("Red Sox", 135252);
teams.set("Huskers", 136923);

let teamArr = Array.from(teams.values());
for (i = 0; i < teamArr.length; i++) {
    console.log(teamArr[i]);
}

//Get the team data so that we can pull the logo image.
async function getTeamData(teamID) {
    let result = await fetch(`https://www.thesportsdb.com/api/v1/json/1/lookupteam.php?id=${teamID}`)
    let teamData = await result.json();
    return teamData.teams[0];
}

//Get the info for the teams last game.
async function getLastGame(teamID) {
    let result = await fetch(`https://www.thesportsdb.com/api/v1/json/1/eventslast.php?id=${teamID}`)
    const lastGames = await result.json();
    const lastGame = lastGames.results[0];
    return lastGame;
};

//Populate the final scores with new HTML after pulling all the info from the API.
for (let i = 0; i < teamArr.length; i++) {
    let homeTeam, awayTeam, homeTeamData, awayTeamData, homeTeamLogo, gameDay;
    getLastGame(teamArr[i])
    .then(lastGame => {
        gameDay = lastGame.dateEvent;
        homeTeam = {
            name: lastGame.strHomeTeam,
            id: lastGame.idHomeTeam,
            score: lastGame.intHomeScore,
        };
        awayTeam = {
            name: lastGame.strAwayTeam,
            id: lastGame.idAwayTeam,
            score: lastGame.intAwayScore
        }; //This is all the info we need except for the team icons.
    }).then(result => {
        homeTeamData = getTeamData(homeTeam.id)
        return homeTeamData; 
    }).then(result => {
        homeTeam.logo = result.strTeamBadge;
    }).then(() => {
        awayTeamData = getTeamData(awayTeam.id)
        return awayTeamData;
    }).then(result => {
        awayTeam.logo = result.strTeamBadge; //After getting who was home and who was away, these let us pull and put the right icon in the table.
    }).then(() => {
        let html = `    <tr>
                            <th><img src="%awayImg%" alt="Away" id="${i}-away" class="team-logo"></th>
                            <th><div class="at-vs">@</div></th>
                            <th><img src="%homeImg" alt="Home" id="${i}-home" class="team-logo"></th>
                        </tr>
                        <tr>
                            <th><div class="away-score">%awayScore%</div></th>
                            <th><div class="gameday">%gameDay%</div></th>
                            <th><div class="home-score">%homeScore%</div></th>
                        </tr>`;
        let newhtml = html.replace(`%awayImg%`, awayTeam.logo + "/preview");
        newhtml = newhtml.replace(`%homeImg`, homeTeam.logo + "/preview");
        newhtml = newhtml.replace(`%awayScore%`, awayTeam.score);
        newhtml = newhtml.replace(`%gameDay%`, gameDay);
        newhtml = newhtml.replace(`%homeScore%`, homeTeam.score);

        document.querySelector(`.past-games-table`).insertAdjacentHTML(`beforeend`, newhtml);

    })
};

【问题讨论】:

    标签: javascript api for-loop asynchronous promise


    【解决方案1】:

    似乎您想要的是发出并发请求以获取数据,但无论它们何时到达,仍然以特定顺序显示它们。

    现在,您正在一个接一个地提出所有请求,并在获得结果时显示结果。这意味着显示结果的顺序是不可预测的,任何先前的请求都可以在任何后续请求之前完成,并且您的数据将无序显示。

    尝试多次运行这个sn -p,显示顺序不可预知:

    注意,在下面的示例中,我使用了一些假函数来表示数据请求并显示它。 fakeRequest 是一个函数,它可以代表你的承诺链中除了最后一个函数之外的所有函数(你用来获取和构建团队数据的所有逻辑),fakeDisplay 是一个函数,可以代表你的链的最后一部分附加 HTML并显示数据。

    const fakeRequest = n => new Promise(
      resolve => setTimeout(() => {
        console.log(`requesting ${n}`);
        resolve(n);
      }, Math.random() * 1000)
    );
    
    const fakeDisplay = n => console.log(`displaying ${n}`);
    
    // all requests are made one after the other
    // and as soon as a request is done, we display
    // the data, no matter which request that is
    for (let i = 0; i < 3; i++) {
      fakeRequest(i).then(fakeDisplay);
    }

    这里的主要问题是您的显示逻辑与获取团队数据的逻辑绑定在一起。拆分此行为将允许您以多种方式解决您的问题。

    解决此问题的一种方法是在前一个请求+显示操作完成之前不发出请求(并且不显示数据):

    const fn = async () => {
      // note: using await is only valid in an async function
      const fakeRequest = n => new Promise(
        resolve => setTimeout(() => {
          console.log(`requesting ${n}`);
          resolve(n);
        }, Math.random() * 1000)
      );
    
      const fakeDisplay = n => console.log(`displaying ${n}`);
    
      // make each request and wait for it to finish
      // then display the result and start next request 
      for (let i = 0; i < 3; i++) {
        const item = await fakeRequest(i);
        fakeDisplay(item);
      }
    }
    
    fn();

    缓解这种情况的另一种方法是等到所有请求完成后再一次显示所有结果(使用Promise.all):

    const fakeRequest = n => new Promise(
      resolve => setTimeout(() => {
        console.log(`requesting ${n}`);
        resolve(n);
      }, Math.random() * 1000)
    );
    
    const fakeDisplay = n => console.log(`displaying ${n}`);
    
    // make the requests one after the other
    // and save all the promises in an array
    const promises = []
    for (let i = 0; i < 3; i++) {
      promises.push(fakeRequest(i));
    }
    
    // then use a built-in utility Promise.all to
    // display all the results once they all arrive
    Promise.all(promises)
      .then(results => {
        for (const item of results) {
          fakeDisplay(item);
        }
      })

    这样做的主要问题是,在完成所有请求之前,您的 UI 不会显示任何内容,这可能需要一些时间,并且可能不是理想的体验。

    如果您几乎在同一时间发出所有请求,但在结果到达时仍以正确的顺序显示结果,也许您的 UX 会是最好的。

    const fn = async () => {
      const fakeRequest = n => new Promise(
        resolve => setTimeout(() => {
          console.log(`requesting ${n}`);
          resolve(n);
        }, Math.random() * 1000)
      );
    
      const fakeDisplay = n => console.log(`displaying ${n}`);
    
      // make the requests one after the other
      // and save all the promises in an array
      const promises = []
      for (let i = 0; i < 3; i++) {
        promises.push(fakeRequest(i));
      }
    
      // loop through the promises and resolve each
      // one and display the data
      for await (const item of promises) {
        fakeDisplay(item);
      }
    }
    
    fn();

    【讨论】:

    • 感谢您的详细回答,并提供了几种不同的解决方法。我最终使用 promises.all 方法使行为正确。非常感谢!
    • @badlytested 乐于助人,愉快的 JavaScript :)
    【解决方案2】:

    您正在混合实现相同目标的两种模式。这是用asyncawait重构的代码

    //Define the teams I want scores from and their API reference numbers.
    let teams = new Map();
    teams.set("Patriots", 134920);
    teams.set("Steelers", 134925);
    teams.set("Bruins", 134830);
    teams.set("Penguins", 134844);
    teams.set("Celtics", 134860);
    teams.set("Red Sox", 135252);
    teams.set("Huskers", 136923);
    
    let teamArr = Array.from(teams.values());
    for (i = 0; i < teamArr.length; i++) {
        console.log(teamArr[i]);
    }
    
    //Get the team data so that we can pull the logo image.
    async function getTeamData(teamID) {
        let result = await fetch(`https://www.thesportsdb.com/api/v1/json/1/lookupteam.php?id=${teamID}`)
        let teamData = await result.json();
        return teamData.teams[0];
    }
    
    //Get the info for the teams last game.
    async function getLastGame(teamID) {
        let result = await fetch(`https://www.thesportsdb.com/api/v1/json/1/eventslast.php?id=${teamID}`)
        const lastGames = await result.json();
        const lastGame = lastGames.results[0];
        return lastGame;
    };
    
    //Populate the final scores with new HTML after pulling all the info from the API.
    for (let i = 0; i < teamArr.length; i++) {
        let homeTeam, awayTeam, homeTeamData, awayTeamData, homeTeamLogo, gameDay;
        const lastGame = await getLastGame(teamArr[i]);
        gameDay = lastGame.dateEvent;
        homeTeam = {
            name: lastGame.strHomeTeam,
            id: lastGame.idHomeTeam,
            score: lastGame.intHomeScore,
        };
        awayTeam = {
            name: lastGame.strAwayTeam,
            id: lastGame.idAwayTeam,
            score: lastGame.intAwayScore
        }; //This is all the info we need except for the team icons.
        homeTeamData = await getTeamData(homeTeam.id);
        homeTeam.logo = homeTeamData.strTeamBadge;
        awayTeamData = await getTeamData(awayTeam.id);
        awayTeam.logo = awayTeamData.strTeamBadge; //After getting who was home and who was away, these let us pull and put the right icon in the table.
        let html = `    <tr>
                            <th><img src="%awayImg%" alt="Away" id="${i}-away" class="team-logo"></th>
                            <th><div class="at-vs">@</div></th>
                            <th><img src="%homeImg" alt="Home" id="${i}-home" class="team-logo"></th>
                        </tr>
                        <tr>
                            <th><div class="away-score">%awayScore%</div></th>
                            <th><div class="gameday">%gameDay%</div></th>
                            <th><div class="home-score">%homeScore%</div></th>
                        </tr>`;
        let newhtml = html.replace(`%awayImg%`, awayTeam.logo + "/preview");
        newhtml = newhtml.replace(`%homeImg`, homeTeam.logo + "/preview");
        newhtml = newhtml.replace(`%awayScore%`, awayTeam.score);
        newhtml = newhtml.replace(`%gameDay%`, gameDay);
        newhtml = newhtml.replace(`%homeScore%`, homeTeam.score);
    
        document.querySelector(`.past-games-table`).insertAdjacentHTML(`beforeend`, newhtml);
    
    };
    

    【讨论】:

      【解决方案3】:

      您可以在async 函数中做的一件事是await Promise 的解析。在for 循环中,这意味着您不会继续执行循环,直到您的上一次迭代解决为止。这是一个简单的例子。如果您将所有提取数据聚合到一个数组中(例如,[getLastGame(teamArr[0]), getLastGame(teamArr[1]), getLastGame(teamArr[2]), etc.]),然后在每个元素上使用 await 循环遍历该数组,您可能会得到类似的结果。

      const timed = (id) => new Promise(res => {
        setTimeout(() => res(id), Math.random() * 2000);
      })
      
      const func = async () => {
        const promises = [timed(1), timed(2), timed(3), timed(4)];
        for (let promise of promises) {
          const returned = await promise;
          console.log(returned);
        }
      }
      
      func();

      您可以考虑的其他事情是,再次汇总您的所有承诺,然后使用Promise.all 等待所有承诺的解决,然后在您的所有数据到达时执行某些操作:

      Promise.all([getLastGame(teamArr[0]), getLastGame(teamArr[1], etc.)])
        .then(all => {
          // the `all` variable will contain an array of all resolved promises
        })
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-08-21
        • 2012-03-10
        • 2021-06-18
        • 1970-01-01
        • 2011-08-16
        • 1970-01-01
        • 2011-03-28
        相关资源
        最近更新 更多