Récursivité avec une API, en utilisant Vanilla JS
Je joue avec l'API Rick et Morty et je souhaite intégrer tous les personnages de l'univers dans un tableau afin de ne pas avoir à faire plus d'appels d'API pour travailler le reste de mon code.
Le point de terminaison https://rickandmortyapi.com/api/character/
renvoie les résultats dans les pages, je dois donc utiliser la récursivité pour obtenir toutes les données en un seul appel API.
Je peux le faire cracher les résultats en HTML mais je n'arrive pas à obtenir un tableau complet d'objets JSON.
J'utilise des idées de récursivité Axios pour paginer une API avec un curseur
J'ai traduit le concept de mon problème, et je l'ai affiché sur mon Codepen Voici le code:
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}`))
Certains yeux et cerveaux acérés seraient utiles. C'est probablement quelque chose de vraiment simple et il me manque juste.
Réponses
La ligne suivante présente des problèmes:
return allPeople.push(populatePeople(res.data.info.next, allPeople))
Ici, vous poussez un objet de promesse dans allPeople
, et en tant que .push()
retourne un nombre, vous renvoyez un nombre, non allPeople
.
Utiliser une for
boucle vers push
des éléments individuels d'un tableau à un autre est en fait une façon verbeuse de copier un tableau. La boucle n'est nécessaire que pour la partie HTML.
De plus, vous mélangez .then()
avec await
, ce qui rend les choses complexes. Utilisez juste await
seulement. Lors de l'utilisation await
, il n'y a plus besoin de récursivité. Remplacez simplement le if
par une boucle:
while (info) {
....
info = res.data.info.next;
}
Vous n'attribuez jamais rien à universePeople
. Vous pouvez supprimer ce paramètre.
Au lieu de la for
boucle simple , vous pouvez utiliser la for...of
syntaxe.
À partir de res
vous n'utilisez que la data
propriété, utilisez une variable pour cette propriété uniquement.
Donc, en prenant tout cela ensemble, vous obtenez ceci:
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;
}
}
Voici un exemple de travail pour la fonction récursive
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()
J'hésite à proposer une autre réponse car l'analyse et la réponse de Trincot sont justes.
Mais je pense qu'une réponse récursive ici peut être assez élégante. Et comme la question a été étiquetée avec «récursion», elle semble utile de la présenter.
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>
Cela ne concerne que la récupération des données. L'ajouter à votre DOM devrait être une étape distincte, et cela ne devrait pas être difficile.
Mise à jour: explication
Un commentaire a indiqué que c'était difficile à analyser. Il y a deux choses qui, j'imagine, pourraient être délicates ici:
Le premier est la déstructuration de l' objet dans
{info: {next}, results} = <...>
. C'est juste un bon moyen d'éviter d'utiliser des variables intermédiaires pour calculer celles que nous voulons réellement utiliser.Le second est la syntaxe de diffusion dans
return [...results, ...<more>]
. Il s'agit d'un moyen plus simple de créer un tableau que d'utiliser.concat
ou.push
. (Il existe une fonctionnalité similaire pour les objets.)
Voici une autre version faisant la même chose, mais avec quelques variables intermédiaires et une concaténation de tableaux à la place. Il fait la même chose:
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)
}
Je préfère la version originale. Mais peut-être trouveriez-vous celui-ci plus clair.