Récursivité avec une API, en utilisant Vanilla JS

Aug 18 2020

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

3 trincot Aug 18 2020 at 13:34

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 forboucle vers pushdes é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 awaitseulement. Lors de l'utilisation await, il n'y a plus besoin de récursivité. Remplacez simplement le ifpar 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 forboucle simple , vous pouvez utiliser la for...ofsyntaxe.

À partir de resvous n'utilisez que la dataproprié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;
    }
}
3 UbeytDemir Aug 18 2020 at 13:17

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

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