Рекурсия с API, используя Vanilla JS

Aug 18 2020

Я играю с API Рика и Морти, и я хочу собрать всех персонажей вселенной в массив, чтобы мне не приходилось делать больше вызовов API для работы остальной части моего кода.

Конечная точка https://rickandmortyapi.com/api/character/возвращает результаты на страницах, поэтому мне приходится использовать рекурсию, чтобы получить все данные за один вызов API.

Я могу заставить его выводить результаты в HTML, но я не могу получить полный массив объектов JSON.

Я использую некоторые идеи из рекурсии Axios для разбивки на страницы api с помощью курсора

Я перевел концепцию своей проблемы и разместил ее в своем Codepen. Это код:

async function populatePeople(info, universePeople){ // Retrieve the data from the API
  let allPeople = []
  let check = ''
  try {
        return await axios.get(info)
            .then((res)=>{
                // here the current page results is in res.data.results
                for (let i=0; i < res.data.results.length; i++){
                    item.textContent = JSON.stringify(res.data.results[i])
                    allPeople.push(res.data.results[i])
                }

                if (res.data.info.next){
          check = res.data.info.next
                return allPeople.push(populatePeople(res.data.info.next, allPeople))
                }
            })
    } catch (error) {
        console.log(`Error: ${error}`) } finally { return allPeople } } populatePeople(allCharacters) .then(data => console.log(`Final data length: ${data.length}`))

Были бы полезны острые глаза и ум. Вероятно, это что-то действительно простое, и мне это просто не хватает.

Ответы

3 trincot Aug 18 2020 at 13:34

В следующей строке есть проблемы:

return allPeople.push(populatePeople(res.data.info.next, allPeople))

Здесь вы вставляете объект обещания allPeopleи, когда .push()возвращает число, вы возвращаете число, а не allPeople.

Использование forцикла для pushотдельных элементов из одного массива в другой - действительно подробный способ копирования массива. Цикл нужен только для HTML-части.

Кроме того, вы смешиваетесь .then()с await, что усложняет ситуацию. Просто используйте awaitтолько. При использовании awaitбольше нет необходимости в рекурсии. Просто замените ifпетлей:

while (info) {
   ....
   info = res.data.info.next;
}

Вы никогда ничего не назначаете universePeople. Вы можете опустить этот параметр.

Вместо простого forцикла вы можете использовать for...ofсинтаксис.

Поскольку resвы используете только dataсвойство, используйте переменную только для этого свойства.

Итак, взяв все это вместе, вы получите следующее:

async function populatePeople(info) {
    let allPeople = [];
    try {
        while (info) {
            let {data} = await axios.get(info);
            for (let content of data.results) {
                const item = document.createElement('li');
                item.textContent = JSON.stringify(content);
                denizens.append(item);
            }
            allPeople.push(...data.results);
            info = data.info.next;
        }
    } catch (error) {
        console.log(`Error: ${error}`)
    } finally {
        section.append(denizens);
        return allPeople;
    }
}
3 UbeytDemir Aug 18 2020 at 13:17

Вот рабочий пример для рекурсивной функции

async function getAllCharectersRecursively(URL,results){
    try{
        const {data} =  await axios.get(URL);
        //  concat current  page results
        results =results.concat(data.results)
        if(data.info.next){
            // if there is next page call recursively
            return await getAllCharectersRecursively(data.info.next,results)
        }
        else{
            // at last page there is no next page so return collected results
            return results
        }
    }
    catch(e){
        console.log(e)
    }
}

async function main(){
    let results = await getAllCharectersRecursively("https://rickandmortyapi.com/api/character/",[])
    console.log(results.length)
}
main()
2 ScottSauyet Aug 18 2020 at 20:03

Я не решаюсь предложить другой ответ, потому что анализ и ответ Тринко точны.

Но я думаю, что рекурсивный ответ здесь может быть довольно элегантным. И поскольку вопрос был помечен как «рекурсия», кажется, его стоит представить.

const populatePeople = async (url) => {
  const {info: {next}, results} = await axios .get (url)
  return [...results, ...(next ? await populatePeople (next) : [])]
}

populatePeople ('https://rickandmortyapi.com/api/character/')
  // or wrap in an `async` main, or wait for global async...
  .then (people => console .log (people .map (p => p .name)))
  .catch (console .warn)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script>/* dummy */ const axios = {get: (url) => fetch (url) .then (r => r .json ())} </script>

Это касается только получения данных. Добавление его в вашу DOM должно быть отдельным шагом, и это не должно быть трудным.

Обновление: объяснение

Комментарий показал, что это сложно разобрать. Есть две вещи, которые, как мне кажется, могут быть здесь сложными:

  • Во-первых, это деструктуризация объекта в {info: {next}, results} = <...>. Это просто хороший способ избежать использования промежуточных переменных для вычисления тех, которые мы действительно хотим использовать.

  • Второй - это синтаксис распространения в return [...results, ...<more>]. Это более простой способ построить массив, чем использование .concatили .push. (Есть аналогичная функция для объектов.)

Вот другая версия, делающая то же самое, но с некоторыми промежуточными переменными и объединением массивов. То же самое:

const populatePeople = async (url) => {
  const response = await axios .get (url)
  const next = response .info && response .info .next
  const results = response .results || []
  const subsequents = next ? await populatePeople (next) : []
  return results .concat (subsequents)
}

Я предпочитаю оригинальную версию. Но, возможно, вам это покажется более понятным.