Recursão com uma API, usando Vanilla JS

Aug 18 2020

Estou brincando com a API de Rick e Morty e quero colocar todos os personagens do universo em um array para que não precise fazer mais chamadas de API para trabalhar o resto do meu código.

O endpoint https://rickandmortyapi.com/api/character/retorna os resultados em páginas, então tenho que usar recursão para obter todos os dados em uma chamada de API.

Posso fazer com que ele exiba os resultados em HTML, mas não consigo obter uma matriz completa de objetos JSON.

Estou usando algumas ideias de recursão do Axios para paginar uma api com um cursor

Traduzi o conceito do meu problema e o postei no meu Codepen. Este é o código:

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}`))

Alguns olhos e cérebros aguçados seriam úteis. Provavelmente é algo muito simples e estou simplesmente perdendo.

Respostas

3 trincot Aug 18 2020 at 13:34

A seguinte linha tem problemas:

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

Aqui você empurra um objeto de promessa para dentro allPeoplee, conforme .push()retorna um número, você está retornando um número, não allPeople.

Usar um forloop para pushitens individuais de um array para outro é realmente uma maneira detalhada de copiar um array. O loop só é necessário para a parte HTML.

Além disso, você está misturando .then()com await, que está fazendo as coisas complexas. Basta usar awaitapenas. Ao usar await, não há mais necessidade de recursão. Basta substituir o ifpor um loop:

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

Você nunca atribui nada a universePeople. Você pode descartar este parâmetro.

Em vez do forloop simples , você pode usar a for...ofsintaxe.

Como resvocê só usa a datapropriedade, use uma variável apenas para essa propriedade.

Juntando tudo isso, você obtém o seguinte:

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

Aqui está um exemplo de trabalho para função recursiva

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

Hesito em oferecer outra resposta porque a análise e a resposta de Trincot são precisas.

Mas acho que uma resposta recursiva aqui pode ser bastante elegante. E como a pergunta foi marcada com "recursão", vale a pena apresentá-la.

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>

Isso se preocupa apenas com a busca de dados. Adicioná-lo ao DOM deve ser uma etapa separada e não deve ser difícil.

Atualização: Explicação

Um comentário indicou que isso é difícil de analisar. Existem duas coisas que eu imagino que podem ser complicadas aqui:

  • A primeira é a desestruturação do objeto em {info: {next}, results} = <...>. Essa é apenas uma boa maneira de evitar o uso de variáveis ​​intermediárias para calcular aquelas que realmente queremos usar.

  • A segunda é a sintaxe de propagação em return [...results, ...<more>]. Esta é uma maneira mais simples de construir um array do que usando .concatou .push. (Há um recurso semelhante para objetos.)

Aqui está outra versão fazendo a mesma coisa, mas com algumas variáveis ​​intermediárias e uma concatenação de array. Ele faz a mesma coisa:

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)
}

Eu prefiro a versão original. Mas talvez você ache este mais claro.