Ricorsione con un'API, utilizzando Vanilla JS

Aug 18 2020

Sto giocando con l'API Rick and Morty e voglio inserire tutti i personaggi dell'universo in un array, quindi non devo fare più chiamate API per lavorare il resto del mio codice.

L'endpoint https://rickandmortyapi.com/api/character/restituisce i risultati in pagine, quindi devo usare la ricorsione per ottenere tutti i dati in una chiamata API.

Posso ottenere che sputi i risultati in HTML ma non riesco a ottenere un array completo di oggetti JSON.

Sto usando alcune idee dalla ricorsione di Axios per impaginare un'api con un cursore

Ho tradotto il concetto per il mio problema e l'ho pubblicato sul mio Codepen Questo è il codice:

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

Alcuni occhi e cervelli acuti sarebbero utili. Probabilmente è qualcosa di veramente semplice e mi manca solo.

Risposte

3 trincot Aug 18 2020 at 13:34

La riga seguente presenta problemi:

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

Qui inserisci un oggetto di promessa allPeoplee, quando .push()restituisce un numero, stai restituendo un numero, non allPeople.

Usare un forciclo per pushsingoli elementi da un array a un altro è davvero un modo prolisso di copiare un array. Il ciclo è necessario solo per la parte HTML.

Inoltre, si stia mescolando .then()con await, che sta facendo le cose complesse. Basta usare awaitsolo. Quando si utilizza await, non è più necessaria la ricorsione. Basta sostituire il ifcon un ciclo:

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

Non assegni mai niente a universePeople. Puoi eliminare questo parametro.

Invece del semplice forciclo, puoi usare la for...ofsintassi.

Dal resmomento che usi solo la dataproprietà, usa una variabile solo per quella proprietà.

Quindi, prendendo tutto questo insieme, ottieni questo:

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

Ecco un esempio funzionante per la funzione ricorsiva

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

Esito a offrire un'altra risposta perché l'analisi e la risposta di Trincot sono azzeccate.

Ma penso che una risposta ricorsiva qui possa essere piuttosto elegante. E poiché la domanda è stata contrassegnata con "ricorsione", sembra che valga 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>

Questo riguarda solo il recupero dei dati. L'aggiunta al tuo DOM dovrebbe essere un passaggio separato e non dovrebbe essere difficile.

Aggiornamento: spiegazione

Un commento ha indicato che questo è difficile da analizzare. Ci sono due cose che immagino potrebbero essere complicate qui:

  • In primo luogo è la destrutturazione oggetto in {info: {next}, results} = <...>. Questo è solo un bel modo per evitare di utilizzare variabili intermedie per calcolare quelle che vogliamo effettivamente utilizzare.

  • Il secondo è la sintassi diffusa in return [...results, ...<more>]. Questo è un modo più semplice per creare un array rispetto all'utilizzo di .concato .push. (C'è una funzionalità simile per gli oggetti.)

Ecco un'altra versione che fa la stessa cosa, ma con alcune variabili intermedie e invece una concatenazione di array. Fa la stessa 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)
}

Preferisco la versione originale. Ma forse potresti trovare questo più chiaro.