web-dev-qa-db-fra.com

Plusieurs appels d'API GET paginés en parallèle/asynchrone dans le noeud

Je fais appel à l'API bitbucket pour obtenir tous les fichiers contenus dans un référentiel. J'ai atteint un point où je peux obtenir la liste de tous les dossiers du référentiel, lancer le premier appel d'API à tous les dossiers racine du référentiel en parallèle et obtenir la liste des 1000 premiers fichiers de tous les dossiers. 

Mais le problème est que bitbucket api ne peut me donner que 1000 fichiers par dossier à la fois. 

J'ai besoin d'ajouter un paramètre de requête & start = nextPageStart et de relancer l'appel jusqu'à ce qu'il soit null et que isLastPage soit à vrai par API. Comment puis-je y parvenir avec le code ci-dessous?

Je reçois le nextPageStart du premier appel à l’API. Voir la réponse de l'API ci-dessous.

Vous trouverez ci-dessous le code que j'ai jusqu'à présent.

Toute aide ou orientation est appréciée.

Réponse de chaque API appelée par dossier.

{
    "values": [
        "/src/js/abc.js",
        "/src/js/efg.js",
        "/src/js/ffg.js",
        ...
    ],
    "size": 1000,
    "isLastPage": false,
    "start": 0,
    "limit": 1000,
    "nextPageStart": 1000
}

fonction où j'ai fait des appels asynchrones pour obtenir la liste des fichiers

export function getFilesList() {
  const foldersURL: any[] = [];
  getFoldersFromRepo().then((response) => {
    const values = response.values;
    values.forEach((value: any) => {
    //creating API URL for each folder in the repo
      const URL = 'https://bitbucket.abc.com/stash/rest/api/latest/projects/'
                   + value.project.key + '/repos/' + value.slug + '/files?limit=1000';
      foldersURL.Push(URL);
        });
    return foldersURL;
      }).then((res) => {
    // console.log('Calling all the URLS in parallel');
    async.map(res, (link, callback) => {
       const options = {
         url: link,
         auth: {
           password: 'password',
           username: 'username',
         },
       };
       request(options, (error, response, body) => {

      // TODO: How do I make the get call again so that i can paginate and append the response to the body till the last page.

         callback(error, body);
       });
     }, (err, results) => {
       console.log('In err, results function');
       if (err) {
         return console.log(err);
       }
       //Consolidated results after all API calls.
       console.log('results', results);
     });
  })
   .catch((error) => error);
}
9
Grinish Nepal

J'ai pu le faire fonctionner en créant une fonction avec rappel.

export function getFilesList() {
  const foldersURL: any[] = [];
  getFoldersFromRepo().then((response) => {
    const values = response.values;
    values.forEach((value: any) => {
    //creating API URL for each folder in the repo
      const URL = 'https://bitbucket.abc.com/stash/rest/api/latest/projects/'
                   + value.project.key + '/repos/' + value.slug + '/files?limit=1000';
      foldersURL.Push(URL);
        });
    return foldersURL;
      }).then((res) => {
    // console.log('Calling all the URLS in parallel');
    async.map(res, (link, callback) => {
       const options = {
         url: link,
         auth: {
           password: 'password',
           username: 'username',
         },
       };
      const myarray = [];
// This function will consolidate response till the last Page per API.
      consolidatePaginatedResponse(options, link, myarray, callback);
     }, (err, results) => {
       console.log('In err, results function');
       if (err) {
         return console.log(err);
       }
       //Consolidated results after all API calls.
       console.log('results', results);
     });
  })
   .catch((error) => error);
}

function consolidatePaginatedResponse(options, link, myarray, callback) {
  request(options, (error, response, body) => {
    const content = JSON.parse(body);
    content.link = options.url;
    myarray.Push(content);
    if (content.isLastPage === false) {
      options.url = link + '&start=' + content.nextPageStart;
      consolidatePaginatedResponse(options, link, myarray, callback);
    } else {
// Final response after consolidation per API
      callback(error, JSON.stringify(myarray));
    }
  });
}
6
Grinish Nepal

Je pense que le meilleur moyen est de l’envelopper dans une vieille école pour la boucle (forEach ne fonctionne pas en async, car c’est synchrone et toutes les requêtes seront générées en même temps).

Ce que j’ai compris, c’est que vous faites une sorte de requête d’amorçage dans laquelle vous obtenez le tableau values, puis vous devriez effectuer une itération parmi les pages. Ici, du code, je n’ai pas bien compris les API, je vais donc vous donner une réponse simplifiée (et, espérons-le, lisible), vous devriez pouvoir l’adapter:

export async function getFilesList() {

    logger.info(`Fetching all the available values ...`);

    await getFoldersFromRepo().then( async values => {

        logger.info("... Folders values fetched.");

        for (let i = 0; ; i++ ) {

            logger.info( `Working on page ${i}`);

            try {
                // if you are using TypeScript, the result is not the promise but the succeeded value already
                const pageResult: PageResult = await yourPagePromise(i);
                if (pageResult.isLastPage) {
                    break;
                }
            } catch(err) {
                console.err(`Error on page ${i}`, err);
                break;
            }

        }

        logger.info("Done.");

    });

    logger.info(`All finished!`);

}

La logique sous-jacente est que la première getFoldersFromRepo() renvoie une promesse qui renvoie les valeurs, puis une itération séquentielle sur toutes les pages disponibles via la fonction yourPagePromise (qui renvoie une promesse). La construction async/wait permet d'écrire du code plus lisible, plutôt que d'avoir une cascade de then ().

Je ne suis pas sûr que cela respecte les spécifications de votre API, mais c'est la logique que vous pouvez utiliser comme base! ^^

1
pierpytom