Recurrencia con una API, usando Vanilla JS
Estoy jugando con la API de Rick and Morty y quiero poner todos los personajes del universo en una matriz para no tener que hacer más llamadas a la API para trabajar con el resto de mi código.
El punto final https://rickandmortyapi.com/api/character/
devuelve los resultados en páginas, así que tengo que usar la recursividad para obtener todos los datos en una llamada a la API.
Puedo hacer que arroje resultados en HTML, pero parece que no puedo obtener una matriz completa de objetos JSON.
Estoy usando algunas ideas de la recursividad de Axios para paginar una API con un cursor
Traduje el concepto para mi problema y lo publiqué en mi Codepen. Este es el 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}`))
Algunos ojos y cerebros agudos serían útiles. Probablemente sea algo realmente simple y me lo estoy perdiendo.
Respuestas
La siguiente línea tiene problemas:
return allPeople.push(populatePeople(res.data.info.next, allPeople))
Aquí inserta un objeto de promesa allPeople
y, como .push()
devuelve un número, está devolviendo un número, no allPeople
.
El uso de un for
bucle para push
elementos individuales de una matriz a otra es realmente una forma detallada de copiar una matriz. El bucle solo es necesario para la parte HTML.
Además, se está mezclando .then()
con await
, que está haciendo las cosas complejas. Solo usa await
solo. Cuando se usa await
, ya no hay necesidad de recursividad. Simplemente reemplace el if
con un bucle:
while (info) {
....
info = res.data.info.next;
}
Nunca asignas nada a universePeople
. Puede eliminar este parámetro.
En lugar del for
bucle simple , puede utilizar la for...of
sintaxis.
Como res
solo usa la data
propiedad, use una variable solo para esa propiedad.
Entonces, tomando todo eso junto, obtienes esto:
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;
}
}
Aquí está un ejemplo de trabajo para la función 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()
Dudo en ofrecer otra respuesta porque el análisis y la respuesta de Trincot son acertados.
Pero creo que una respuesta recursiva aquí puede ser bastante elegante. Y como la pregunta fue etiquetada con "recursividad", parece que vale la pena presentarla.
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>
Esto solo se ocupa de obtener los datos. Agregarlo a su DOM debería ser un paso separado y no debería ser difícil.
Actualización: explicación
Un comentario indicó que esto es difícil de analizar. Hay dos cosas que imagino que podrían ser complicadas aquí:
En primer lugar es la desestructuración objeto en
{info: {next}, results} = <...>
. Esta es solo una buena manera de evitar el uso de variables intermedias para calcular las que realmente queremos usar.El segundo es la sintaxis de propagación en
return [...results, ...<more>]
. Esta es una forma más sencilla de construir una matriz que usar.concat
o.push
. (Hay una característica similar para los objetos).
Aquí hay otra versión que hace lo mismo, pero con algunas variables intermedias y una concatenación de matrices. Hace la misma cosa:
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)
}
Prefiero la versión original. Pero tal vez encuentre esto más claro.