【问题标题】:React Promise asynchronous tasks order not correctReact Promise 异步任务顺序不正确
【发布时间】:2020-09-13 19:44:21
【问题描述】:

我正在使用 Promise 进行多次调用。

我要获取的 API 端点是:

  1. https://www.api-football.com/demo/v2/statistics/357/5/2019-08-30
  2. https://www.api-football.com/demo/v2/statistics/357/5/2019-09-30
  3. https://www.api-football.com/demo/v2/statistics/357/5/2019-10-30

查看代码

export function getTeamsStats(league, team, type) {
  return function(dispatch) {

    const url = "https://www.api-football.com/demo/v2/statistics";
    let dates = ["2019-08-30", "2019-09-30", "2019-10-30"];
    const getAllData = (dates, i) => {
      return Promise.allSettled(dates.map(x => url + '/' + 357 + '/' + 5 + '/' + x).map(fetchData));
    }

    const fetchData = (URL) => {
      return axios
        .get(URL)
        .then(res => {
          const {
            matchsPlayed: { total: teamsTotalMatchsPlayed},
          } = res.data.api.statistics.matchs;

          const matchsPlayed = teamsTotalMatchsPlayed;

          dispatch(receivedTeamsStat(matchsPlayed, type));
        })
        .catch(e => {
          console.log(e);
        });
    }

    getAllData(dates).then(resp=>{console.log(resp)}).catch(e=>{console.log(e)})

  }
}

然后在我的组件中,我将这个特定球队(在本例中为圣保罗)从初始日期 30-8-201930-10 进行的比赛放入一个数组中-2019

    const [dataHomeTeam, setDataHomeTeam] = useState([]);

      useEffect(() => {
    
         if (!team.matchsPlayed) {
           return ;
         }
    
         setDataHomeTeam(prev =>
           prev.concat([
             {
               matches: team.matchsPlayed,
             }
           ])
         );
    
       },[team.matchsPlayed]);

console.log('Data Array', dataHomeTeam);

问题在于,通常在页面的第一次渲染中,我的匹配顺序是正确的,从 30-8-2019 到 30-10-2019

查看控制台日志图像

但有时不是,请看这里

所以问题是,我如何确保 Promise 向我返回请求的正确顺序?

我用Promise.allSettled,多个异步任务互不依赖成功完成,但顺序不总是对的。

【问题讨论】:

  • 你试过Promise.all吗?
  • 是的,没有区别
  • 现在我仔细观察了它,你并没有真正从fetchData 承诺中返回任何东西,但你正在做某种dispatch,目前还不清楚这是从哪里来的,所以我猜你看到的问题是这些调度不是按顺序触发的(除非你很幸运,否则它们不应该触发),但是如果你调用getAllData,它返回一个Promise.all,其中fetchData返回then 中的最终结果(而不是调用 dispatch),那么你会得到正确的行为。
  • 您介意用代码示例写答案以更好地理解吗?
  • 不清楚您是如何在组件中使用getTeamsStats 函数的。

标签: javascript reactjs promise react-hooks use-effect


【解决方案1】:

Promise.all 保存的顺序。您应该先等待所有 api 承诺解决,然后再进行操作分配。

有用的文章:Promise.all: Order of resolved values

【讨论】:

    【解决方案2】:

    你做得怎么样,

    function updateUI(value) {
        console.log(value);
        // Do something to update the UI.
    }
    
    // Note that order of resolution of Promises is 2, 1, 3
    const promise1 = new Promise((resolve) => setTimeout(resolve, 200, 1)).then(updateUI);
    const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 2)).then(updateUI);
    const promise3 = new Promise((resolve) => setTimeout(resolve, 300, 3)).then(updateUI);
    const promises = [promise1, promise2, promise3];
    
    Promise.allSettled(promises).
      then((value) => console.log('Nothing to do here', value));
    
    // Output: 2, 1, 3
    
    // Here we update the UI as soon as the result is obtained. As a result, the UI is also updated in the
    // order in which the promise was resolved.
    
    

    换句话说,我们不仅要等待网络调用,还要等待网络调用和 UI 更新都为每个你不想要的 id 完成。

    你应该怎么做,

    // Note that order of resolution of Promises is 2, 1, 3 (Same as previous)
    
    const promise1 = new Promise((resolve) => setTimeout(resolve, 200, 1));
    const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 2));
    const promise3 = new Promise((resolve) => setTimeout(resolve, 300, 3));
    const promises = [promise1, promise2, promise3];
    
    
    Promise.allSettled(promises).
      then((results) => results.forEach((result) => updateUI(result.value)));
    
    // Output: 1, 2, 3
    
    // Here, we wait for all the network requests to complete and then loop through the results and update the UI.
    // This ensures that the result is in order.
    

    如果您不想等待所有 Promise 解决并希望在一个解决后立即更新 UI 并仍然保持顺序,那么您需要传递数组中元素的位置,然后使用该位置更新数组中给定位置的元素。

    希望这会有所帮助。

    【讨论】:

      【解决方案3】:

      这应该通过Promise.all 解决,因为如文档中所述

      返回值将按照 Promise 传递的顺序,不管 完成顺序。

      您遇到的问题是,您通过调用dispatch 来设置您的状态,基于它的完成情况,并且因为承诺可以在任何给定时间完成(例如,第三个请求可能首先完成,因为它们是同时发送的),dispatch 将被调用,并且您的状态管理将其设置为第一个响应(这就是为什么您“有时”会遇到这种行为,因为这取决于网络谁先完成)。

      这意味着您应该更改您的dispatch 以接收已完成的承诺数组,或者在完成后一个接一个地调用dispatch,如下面的代码所示 - 全部解决并按顺序发送:

      export function getTeamsStats(league, team, type) {
          return function (dispatch) {
          const url = "https://www.api-football.com/demo/v2/statistics";
          let dates = ["2019-08-30", "2019-09-30", "2019-10-30"];
          const getAllData = (dates, i) => {
              return Promise.all(dates.map(x => url + '/' + 357 + '/' + 5 + '/' + x).map(fetchData));
          }
      
          const fetchData = (URL) => {
              return axios
                  .get(URL)
                  .then(res => {
                      const {
                          matchsPlayed: { total: teamsTotalMatchsPlayed },
                      } = res.data.api.statistics.matchs;
      
                      return teamsTotalMatchsPlayed;
                  })
                  .catch(e => {
                      console.log(e);
                  });
          }
      
          getAllData(dates).then(resp => {
              console.log(resp)
      
              // 'resp' here are all 'teamsTotalMatchsPlayed' in correct order (here I mean order of call, not promise completion)
              // so just dispatch them in order
              resp.map(matchsPlayed => receivedTeamsStat(matchsPlayed, type));            
          }).catch(e => { 
              console.log(e) 
          })
      
         }
      }
      

      请注意,我可能犯了一些语法错误,但你明白了。

      【讨论】:

      • 基于 dispatch 调用,您似乎正在使用 redux 来管理此处的状态。如果您仍然希望调度实时发生,您可以键入结果(允许您按所需顺序选择结果)或从初始映射中传递一个索引以允许事后排序:return Promise.all(dates.map((x, index) => url + '/' + 357 + '/' + 5 + '/' + x).map(fetchData, index));
      【解决方案4】:
      export function getTeamsStats(league, team, type) {
        return function (dispatch) {
          const URL = "https://www.api-football.com/demo/v2/statistics";
          let DATES = ["2019-08-30", "2019-09-30", "2019-10-30"];
      
          return Promise.all(
            DATES.map(date => axios.get(`${URL}/357/5/${date}`))
              .then(responseList => {
                responseList.map((res) => {
                  const {
                    matchsPlayed: { total: teamsTotalMatchsPlayed },
                  } = res.data.api.statistics.matchs;
      
                  const matchsPlayed = teamsTotalMatchsPlayed;
      
                  dispatch(receivedTeamsStat(matchsPlayed, type));
                });
              })
              .catch((e) => console.log(e))
          );
        };
      }
      

      【讨论】:

        【解决方案5】:

        实现您的要求的理想方式是使用Promise.all()。主要有两个原因,

        • 按照 Promise 传递的顺序维护返回值,而不考虑完成顺序。

        返回值将按照 Promise 传递的顺序,不管 完成顺序。

        参考link中的返回值部分

        • 如果 iterable 中的任何 Promise 被拒绝,则拒绝返回的 Promise(短路)。

        这也很重要。如果 fetchData 的第一个可迭代 Promise 被拒绝,我们不需要等待所有异步可迭代 Promise 被解析/拒绝,我们可以短路并拒绝返回的 Promise。

        另一方面Promise.allSettled()

        Promise.allSettled() 方法返回一个 Promise,它在所有给定的 Promise 都已实现或拒绝后解析,并带有 对象数组,每个对象都描述了每个承诺的结果。

        它也不维护返回的承诺中的顺序。

        Promise.allSettled() 方法永远不会短路。始终兑现承诺,从不拒绝。

        Promise.all() 和 Promise.allSettled() 参考下面的对照表,

        <script src="https://gist.github.com/Seralahthan/9934ba2bd185a8ccfbdd8e4b3523ea23.js"></script>

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-12-15
          • 1970-01-01
          • 2016-07-16
          • 1970-01-01
          • 2011-05-07
          相关资源
          最近更新 更多