循环内 Promise 值的 setState

setState from Promise values inside loop

我正在使用 React 和 axios 进行外部 API 调用,遍历传递回 API 调用的数组中的每个对象。在循环内部,我有一个承诺调用了另一个 returns 对象的函数。我想使用此对象返回的值并将它们分配给循环外的变量,该变量是用于设置状态的数组但我似乎无法这样做,因为它总是空的?希望下面我的代码中的评论能帮助您理解我的问题。

let self = this;
this.instance.get('/fixtures?timeFrame=n1').then((fixtures) => {
  // get all fixtures
  const allFixtures = fixtures.data.fixtures;
  // create valid fixtures array to add all fixture details to pass to fixtures state
  let validFixtures = [];
  // loop through all fixture objects in allFixtures array
  for (var i = 0; i < (allFixtures.length); i++) {
    // check if valid fixture, returns true or false
    let isValid = self.isValid(allFixtures[i]);
    // if fixture is valid
    if (isValid) {
      // get id of fixture to pass through to fixture route with id query
      let fixtureId = allFixtures[i]._links.self.href.split('v1/')
        .pop();
      // home teams name
      let homeTeam = allFixtures[i].homeTeamName;
      // away teams name
      let awayTeam = allFixtures[i].awayTeamName;
      // call head2head function to get all previous results from the two teams playing and returns average score
      // returns as object, example: { 'homeTeamAvgScore': 2, 'awayTeamAvgScore': 1 }
      self.getHead2Head(fixtureId, homeTeam,
        awayTeam).then((avg) => {
        //in here i want to push object into validFixtures array along with homeTeam and awayTeam as named values
        return validFixtures.push({
          'homeTeam': homeTeam,
          'awayTeam': awayTeam,
          'homeTeamAvgScore': avg.homeTeamAvgScore,
          'awayTeamAvgScore': avg.awayTeamAvgScore
        })
      });
    }
  }
  //validFixtures is empty??? 
  //How else can push to array and then later setState to fixtures with validFixtures array???
  self.setState({
    fixtures: validFixtures
  });
}).catch((error) => {
  console.log(error);
});
}

.then 处理程序始终被异步调用。因此,在您的情况下 validFixtures.push() 将比 self.setState({ fixtures: validFixtures });

执行得更晚(原文如此!)

如何修复:

1) 旧的 JS 方式。

  let validFixtures = [];
  let promieses = [];
  for (...) {
    ...
      promises.push(self.getHead2Head(fixtureId, homeTeam,
        awayTeam).then((avg) => {
        //in here i want to push object into validFixtures array along with homeTeam and awayTeam as named values
        return validFixtures.push({
          'homeTeam': homeTeam,
          'awayTeam': awayTeam,
          'homeTeamAvgScore': avg.homeTeamAvgScore,
          'awayTeamAvgScore': avg.awayTeamAvgScore
        })
      }));
    ...
  }

  Promise.all(promises).then(() => {
    self.setState({
      fixtures: validFixtures
    });
  });

2) 现代 JS 方式(注意 asyncawait 关键字):

let self = this;
this.instance.get('/fixtures?timeFrame=n1').then(async (fixtures) => {
    // get all fixtures
    const allFixtures = fixtures.data.fixtures;
    // create valid fixtures array to add all fixture details to pass to fixtures state
    let validFixtures = [];
    // loop through all fixture objects in allFixtures array
    for (var i = 0; i < (allFixtures.length); i++) {
        // check if valid fixture, returns true or false
        let isValid = self.isValid(allFixtures[i]);
        // if fixture is valid
        if (isValid) {
            // get id of fixture to pass through to fixture route with id query
            let fixtureId = allFixtures[i]._links.self.href.split('v1/')
                .pop();
            // home teams name
            let homeTeam = allFixtures[i].homeTeamName;
            // away teams name
            let awayTeam = allFixtures[i].awayTeamName;
            // call head2head function to get all previous results from the two teams playing and returns average score
            // returns as object, example: { 'homeTeamAvgScore': 2, 'awayTeamAvgScore': 1 }
            const avg = await self.getHead2Head(fixtureId, homeTeam, awayTeam);
            //in here i want to push object into validFixtures array along with homeTeam and awayTeam as named values
            validFixtures.push({
                'homeTeam': homeTeam,
                'awayTeam': awayTeam,
                'homeTeamAvgScore': avg.homeTeamAvgScore,
                'awayTeamAvgScore': avg.awayTeamAvgScore
            });
        }
    }
    //validFixtures is empty??? 
    //How else can push to array and then later setState to fixtures with validFixtures array???
    self.setState({
        fixtures: validFixtures
    });
}).catch((error) => {
    console.log(error);
});

此特定要求称为障碍。也就是说,您想等到 n 个任务完成后,再做某事。 "wait till n number of tasks finish" 部分可以使用屏障来实现。

如果您使用 Promises,这可以使用 Promise.all 轻松完成。 Axios 公开了 promise 接口。

如果您不想使用 Promises,您要么必须使用 async npm 库之类的东西,要么自己实施一个屏障。

更新:

Async - Await 与其他答案之一中提到的 Promise.all 不同。建议的方法会降低性能,因为循环将一个接一个地同步 运行。这在 MDN docs 中解释得很清楚。

示例修复,

this.instance.get('/fixtures?timeFrame=n1')
    .then((fixtures) => {
        // ...Same code as yours
        const allFixtures = fixtures.data.fixtures;
        let promises = [];

        for (let i = 0; i < (allFixtures.length); i++) {
            // ... Same code as yours
            if (isValid) {
                // ... Same code as yours

                // Don't call "then". We will resolve these promises later
                promises.push(this.getHead2Head(fixtureId, homeTeam, awayTeam));
            }
        }

        Promise.all(promises)
            .then(averages=>{
                let validFixtures = averages.map((avg, index)=>{
                    return {
                        'homeTeam': allFixtures[index].homeTeamName,
                        'awayTeam': allFixtures[index].awayTeamName,
                        'homeTeamAvgScore': avg.homeTeamAvgScore,
                        'awayTeamAvgScore': avg.awayTeamAvgScore
                    };
                });
                this.setState({
                    fixtures: validFixtures
                });
            });
    })
    .catch((error) => {
        console.log(error);
    });

一些旁注:

  1. 这里不需要 self 变量。 this 只要您使用箭头函数 (=>) 而不是 function 关键字,范围就不会改变。
  2. 我缩进 then 解析的方式有点不同。那只是因为它对我来说更具可读性