Неправильный порядок асинхронных задач React Promise

Я делаю несколько звонков с 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-2019 по 30-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, несколько асинхронных задач, которые не зависят друг от друга для успешного завершения, но порядок не всегда правильный.


person Koala7    schedule 26.05.2020    source источник
comment
Вы пробовали Promise.all?   -  person goto1    schedule 08.09.2020
comment
Да без разницы   -  person Koala7    schedule 08.09.2020
comment
Теперь, когда я смотрю на это внимательно, вы на самом деле ничего не возвращаете из обещания fetchData, но вы делаете что-то вроде dispatch, что неясно, откуда это берется, поэтому я предполагаю, что проблема, которую вы видите, заключается в том, что эти отправки запускаются не по порядку (что не должно происходить, если только вам не повезет), но если вы вызовете getAllData, который возвращает Promise.all, где fetchData возвращает окончательный результат внутри then (вместо вызова отправки), вы получите правильное поведение.   -  person goto1    schedule 08.09.2020
comment
Не могли бы вы написать ответ с примером кода, чтобы лучше понять?   -  person Koala7    schedule 08.09.2020
comment
Непонятно, как вы используете функцию getTeamsStats внутри вашего компонента.   -  person goto1    schedule 08.09.2020
comment
Я предоставлю codesanbox   -  person Koala7    schedule 08.09.2020
comment
Я создал образец здесь codesandbox.io/s/bitter -water-8kc6t?file=/src/index.js, используя Promise.allSettled. Он возвращается в том же порядке, что и они. Мое предложение здесь состоит в том, чтобы дождаться, пока Promise.allSettled разрешится с правильным статусом, а затем соответствующим образом отправить данные.   -  person Pranay Tripathi    schedule 08.09.2020


Ответы (4)


Это должно быть решено через Promise.allпотому что как описано в документации

Возвращаемые значения будут в порядке переданных промисов, независимо от порядка завершения.

Проблема заключается в том, что вы устанавливаете свое состояние, вызывая dispatch для каждого промиса на основе его завершения, и поскольку промисы могут завершиться в любой момент времени (например, 3-й запрос может завершиться первым, потому что они отправляются одновременно), 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) 
    })

   }
}

Обратите внимание, что я, возможно, сделал некоторую синтаксическую ошибку, но вы поняли идею.

person zhuber    schedule 09.09.2020
comment
Основываясь на вызове dispatch, похоже, что вы используете избыточность для управления состоянием здесь. Если вы по-прежнему хотите, чтобы отправки происходили в режиме реального времени, вы можете либо ввести результаты (что позволит вам выбирать результаты в том порядке, в котором вы хотите), либо передать индекс из исходной карты, чтобы впоследствии можно было сортировать: return Promise.all(dates.map((x, index) => url + '/' + 357 + '/' + 5 + '/' + x).map(fetchData, index)); - person aaronrhodes; 14.09.2020

Идеальный способ выполнить ваше требование — использовать Обещание.все(). Это связано с двумя основными причинами,

  • Чтобы сохранить возвращаемое значение в порядке переданных промисов, независимо от порядка завершения.

Возвращаемые значения будут в порядке переданных промисов, независимо от порядка завершения.

См. раздел Возвращаемое значение в ссылка

  • Чтобы отклонить возвращенное обещание (короткое замыкание), если какое-либо из обещаний в итерируемом объекте отклонено.

Это тоже важно. Нам не нужно ждать, пока все асинхронные итерируемые обещания будут разрешены/отклонены, если первое итерируемое обещание fetchData отклоняется, мы можем замкнуть цепь и отклонить возвращенное обещание.

С другой стороны, Promise.allSettled()< / а>,

Метод Promise.allSettled() возвращает промис, который разрешается после того, как все заданные промисы либо выполнены, либо отклонены, с массивом объектов, каждый из которых описывает результат каждого промиса.

Он также не поддерживает порядок в возвращенном промисе.

Метод Promise.allSettled() никогда не дает короткого замыкания. Обещание всегда выполняется, никогда не отвергается.

Обратитесь к следующей таблице сравнения Promise.all() и Promise.allSettled(),

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

person Seralahthan    schedule 15.09.2020

Как вы сделали,

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.

Другими словами, мы ждем не только сетевого вызова, но и завершения сетевого вызова и обновления пользовательского интерфейса для каждого идентификатора, который вам не нужен.

Как вы должны были сделать вместо этого,

// 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.

Если вы не хотите ждать разрешения всех промисов и хотите обновить пользовательский интерфейс, как только одно разрешится, и при этом сохранить порядок, вам нужно передать позицию элемента в массиве, а затем использовать эту позицию для обновления элемент на месте в заданной позиции в массиве.

Надеюсь это поможет.

person sidthesloth    schedule 08.09.2020

Promise.all сохраняет порядок. Вы должны дождаться, пока все промисы API будут разрешены, прежде чем отправлять действия.

Полезная статья: Promise.all: порядок разрешенных значений

person Dolly    schedule 08.09.2020