Recursão com uma API, usando Vanilla JS
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
A seguinte linha tem problemas:
return allPeople.push(populatePeople(res.data.info.next, allPeople))
Aqui você empurra um objeto de promessa para dentro allPeople
e, conforme .push()
retorna um número, você está retornando um número, não allPeople
.
Usar um for
loop para push
itens 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 await
apenas. Ao usar await
, não há mais necessidade de recursão. Basta substituir o if
por um loop:
while (info) {
....
info = res.data.info.next;
}
Você nunca atribui nada a universePeople
. Você pode descartar este parâmetro.
Em vez do for
loop simples , você pode usar a for...of
sintaxe.
Como res
você só usa a data
propriedade, 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;
}
}
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()
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.concat
ou.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.