Синхронно заполнять / изменять массив и возвращать измененный массив внутри обещания - PullRequest
5 голосов
/ 05 мая 2020

Я новичок в ReactJS и redux, так что мне никогда раньше не приходилось с этим работать. Я получаю данные из вызова API в своем проекте. Я хочу изменить данные, добавив к объекту новое свойство. Однако, поскольку код не запускается синхронно, возвращается немодифицированный массив (я предполагаю) вместо модифицированного массива.

export function loadThings() {
  return dispatch => {
    return dispatch({
     type: 'LOAD_THINGS',
     payload: {
       request: {
         url: API_GET_THINGS_ENDPOINT,
         method: 'GET'
       }
     }
    }).then(response => {
      let things = response.payload.data;
      // Retrieve all items for the loaded "things"
      if(things) {
        things.forEach((thing, thingIndex) => {
            things[thingIndex].items = []
            if (thing.hasOwnProperty('channels') && thing.channels) {
              thing.channels.forEach(channel => {
                if (channel.hasOwnProperty('linkedItems') && channel.linkedItems) {
                  channel.linkedItems.forEach(itemName => {
                    dispatch(loadItems(itemName)).then(
                      item => things[thingIndex].items.push(item) // push the items inside the "things"
                    )
                  })
                }
              })
            }
        })
      }
      things.forEach(data => console.log(data.items.length, data.items)) // data.items.length returns 0, data.items returns a populated array
      return things // return the modified array
    }).catch(error => {
      //todo: handle error
      return false
    })
  }
}

Как видите, я выполняю вызов API, который возвращает данные с именем response. Массив заполнен всеми «вещами». Если things существует, я хочу загрузить дополнительную информацию с именем «items». Основываясь на информации в массиве things, я выполню еще один вызов API (который выполняется путем отправки функции loadItems), который возвращает еще один promise. Основываясь на данных в результатах этого вызова API, я вставлю sh в свойство items (которое является массивом) объекта things.

Как вы можете видеть в комментариях , если я l oop через массив things и регистрирую свойство items, которое я только что создал, оно в основном возвращает 0 как длину, что означает, что массив things возвращается до того, как массив things будет изменен.

Я хотел бы знать две вещи:

  • Что заставляет мой код запускаться asyn c. Это функция dispatch(loadItems(itemName)), поскольку она возвращает обещание?
  • Как я могу синхронно выполнить свой код?

Обратите внимание: эта функция loadThings() также возвращает обещание (если вы не знакомы с redux).

Возможно, вам будет интересно узнать, что я сам пробовал исправить код

Так как я не понимаю лог c почему код запускается asyn c, я пробовал безнадежные вещи. Например, обернуть код другим Promise.all и вернуть измененный массив в этом обещании. Я использовал метод then этого обещания для изменения массива things, который дал точно такой же результат. Вероятно, потому что return things выполняется за пределами этого promise.

Мне бы очень хотелось знать, что происходит

Edit Я добавил loadItems() код вопроса, по запросу:

export function loadItems(itemName) {
  return dispatch => {
    const url = itemName ? API_GET_ITEMS_ENDPOINT + `/${itemName}` : API_GET_ITEMS_ENDPOINT;
    return dispatch({
      type: 'LOAD_ITEMS',
      payload: {
        request: {
          url: url,
          method: 'GET'
        }
      }
    }).then(response => {
      return response.payload.data
    })
  }
}

Ответы [ 2 ]

4 голосов
/ 07 мая 2020

Мой подход заключался бы в отображении более things, создавая массивы обещаний для всех их элементов, завернутых в Promise.all, что дает вам массив Promise.all.

Затем вы return things и этот массив обещаний в другом Promise.all и в следующем блоке then, вы можете просто назначить массивы каждому thing с простым for l oop:

export function loadThings() {
  return dispatch => {
    return dispatch({
      type: 'LOAD_THINGS',
      payload: {
        request: {
          url: API_GET_THINGS_ENDPOINT,
          method: 'GET'
        }
      }
    }).then(response => {
      let things = response.payload.data;
      // Retrieve all items for the loaded "things"
      const items = things.map((thing) => {
        const thingItems = []
        if (thing.hasOwnProperty('channels') && thing.channels) {
          thing.channels.forEach(channel => {
            if (channel.hasOwnProperty('linkedItems') && channel.linkedItems) {
              channel.linkedItems.forEach(itemName => {
                thingItems.push(dispatch(loadItems(itemName)));
              });
            }
          });
        }

        return Promise.all(thingItems);
      });

      return Promise.all([things, Promise.all(items)])
    })
    .then(([things, thingItems]) => {
      things.forEach((thing, index) => {
        thing.items = thingItems[index];
      })

      return things;
    })
    .catch(error => {
      //todo: handle error
      return false
    })
  }
}

Изменить: вам нужно pu sh вызовы dispatch(loadItmes(itemName)) непосредственно в thingItems.

3 голосов
/ 07 мая 2020

Я думаю, вы могли бы реорганизовать его следующим образом:

export function loadThings() {
  return dispatch => {
    return dispatch({
     type: 'LOAD_THINGS',
     payload: {
       request: {
         url: API_GET_THINGS_ENDPOINT,
         method: 'GET'
       }
     }
    }).then(response => {
      let things = response.payload.data;
      // Retrieve all items for the loaded "things"
      if( things ) {
        return Promise.all( things.reduce( (promises, thing) => {
          if (thing.channels) {
            thing.items = [];
            promises.push( ...thing.channels.map( channel => 
              channel.linkedItems && 
              channel.linkedItems.map( item => 
                loadItems(item).then( result => thing.items.push( result ) ) 
              ) ).flat().filter( i => !!i ) );
          }
          return promises;
        }, []) );
      }
      return things;
    }).catch(error => {
      //todo: handle error
      return false
    })
  }
}

В случае, если у вас будет things, он проверит каналы и linkedItems для этого канала и создаст обещание который вернет результат sh в массив thing.items.

При возврате Promise.all продолжение loadThings будет завершено только после разрешения Promise.all. В случае, если ничего нет, просто вещи возвращаются (что было бы ложным значением, поэтому мне интересно, насколько достоверным может быть это утверждение)

Я на самом деле не тестировал рефакторинг, поэтому могут быть некоторые скобки вам нужно приспособиться к вашей ситуации, но я думаю, это дает вам представление?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...