Ricorsione con un'API, utilizzando Vanilla JS
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
La riga seguente presenta problemi:
return allPeople.push(populatePeople(res.data.info.next, allPeople))
Qui inserisci un oggetto di promessa allPeople
e, quando .push()
restituisce un numero, stai restituendo un numero, non allPeople
.
Usare un for
ciclo per push
singoli 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 await
solo. Quando si utilizza await
, non è più necessaria la ricorsione. Basta sostituire il if
con un ciclo:
while (info) {
....
info = res.data.info.next;
}
Non assegni mai niente a universePeople
. Puoi eliminare questo parametro.
Invece del semplice for
ciclo, puoi usare la for...of
sintassi.
Dal res
momento che usi solo la data
proprietà, 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;
}
}
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()
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.concat
o.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.