Recurrencia con una API, usando Vanilla JS

Aug 18 2020

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

3 trincot Aug 18 2020 at 13:34

La siguiente línea tiene problemas:

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

Aquí inserta un objeto de promesa allPeopley, como .push()devuelve un número, está devolviendo un número, no allPeople.

El uso de un forbucle para pushelementos 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 awaitsolo. Cuando se usa await, ya no hay necesidad de recursividad. Simplemente reemplace el ifcon un bucle:

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

Nunca asignas nada a universePeople. Puede eliminar este parámetro.

En lugar del forbucle simple , puede utilizar la for...ofsintaxis.

Como ressolo usa la datapropiedad, 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;
    }
}
3 UbeytDemir Aug 18 2020 at 13:17

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()
2 ScottSauyet Aug 18 2020 at 20:03

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 .concato .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.