0

I'm working with fetching information from a github repository. I want to get the list of pull requests within that repo, get the list of commits associated with each pull request, then for each commit I want to get information such as the author of the commit, the number of files associated with each commit and the number of additions and deletions made to each file. I'm using axios and the github API to accomplish this. I know how to work with the API, but the promises and async functions are keeping me from accomplishing my task. I have the following code:

const axios = require('axios');
var mapOfInformationObjects = new Map();
var listOfCommits = [];
var listOfSHAs = [];
var gitApiPrefix = link I'll use to start fetching data;
var listOfPullRequestDataObjects = [];
var listOfPullRequestNumbers = [];
var mapOfPullNumberToCommits = new Map();

function getAllPullRequests(gitPullRequestApiLink) {
    return new Promise((resolve, reject) => {
        axios.get(gitPullRequestApiLink).then((response) =>{
            listOfPullRequestDataObjects = response['data'];
        var k;
        for (k = 0; k < listOfPullRequestDataObjects.length; k++){
            listOfPullRequestNumbers.push(listOfPullRequestDataObjects[k]['number']);
        }
        resolve(listOfPullRequestNumbers);
    }).catch((error) => {
        reject(error);
    })
})
}

function getCommitsForEachPullRequestNumber(listOfPRNumbers) {
var j;
for (j = 0; j < listOfPRNumbers.length; j++) {
    currPromise = new Promise((resolve, reject) => {
        currentGitApiLink = gitApiPrefix + listOfPRNumbers[j] + "/commits";
        axios.get(currentGitApiLink).then((response) => {
            mapOfPullNumberToCommits.set(listOfPRNumbers[j], response['data']);
            resolve("Done with Pull Request Number: " + listOfPRNumbers[j]);
        }).catch((error) => {
            reject(error);
        })
    })
}

}

function getListOfCommits(gitCommitApiLink){
return new Promise((resolve, reject) => {
    axios.get(gitCommitApiLink).then((response) => {
        resolve(response);
    }).catch((error) => {
        reject(error);
    })
})
}

So far, I made some functions that I would like to call sequentially. First I'd like to call getAllPullRequestNumbers(someLink) Then I'd like to call getCommitsForEachPullRequestNumber(listofprnumbers) Then getListOfCommits(anotherLink)

So it would look something like

getAllPullRequestNumbers(someLink)
getCommitsForEachPullRequestNumber(listofprnumbers)
getListOfCommits(anotherlink)

But two problems arise: 1) I'm not sure if this is how you would call the functions so that the first function in the sequence completes before the other. 2) Because I'm not familiar with Javascript, I'm not sure, especially with the getCommitsForEachPullRequestNumber function since you run a loop and call axios.get() on each iteration of the loop, if this is how you work with promises within the functions.

Would this be how you would go about accomplishing these two tasks? Any help is much appreciated. Thanks!

kostix
  • 51,517
  • 14
  • 93
  • 176
  • can you provide an **api link** you want to work with ? – gui3 Nov 23 '19 at 17:30
  • https://api.github.com/repos/elixir-lang/elixir/ –  Nov 23 '19 at 17:36
  • Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! – Bergi Nov 23 '19 at 18:48

2 Answers2

1

When you a number of asynchronous operations (represented by promises) that you can run all together and you want to know when they are all done, you use Promise.all(). You collect an array of promises and pass it to Promise.all() and it will tell you when they have all completed or when one of them triggers an error. If all completed, Promise.all() will return a promise that resolves to an array of results (one for each asynchronous operation).

When you're iterating an array to do your set of asynchronous operations, it then works best to use .map() because that helps you create a parallel array of promises that you can feed to Promise.all(). Here's how you do that in getCommitsForEachPullRequestNumber():

function getCommitsForEachPullRequestNumber(listOfPRNumbers) {
    let mapOfPullNumberToCommits = new Map();
    return Promise.all(listOfPRNumbers.map(item => {
        let currentGitApiLink = gitApiPrefix + item + "/commits";
        return axios.get(currentGitApiLink).then(response => {
            // put data into the map
            mapOfPullNumberToCommits.set(item, response.data);
        });
    })).then(() => {
        // make resolved value be the map we created, now that everything is done
        return mapOfPullNumberToCommits;
    });
}

// usage:
getCommitsForEachPullRequestNumber(list).then(results => {
    console.log(results);
}).catch(err => {
    console.log(err);
});

Then, in getListOfCommits(), since axios already returns a promise, there is no reason to wrap it in a manually created promise. That is, in fact, consider a promise anti-pattern. Instead, just return the promise that axios already returns. In fact, there's probably not even a reason to have this as a function since one can just use axios.get() directly to achieve the same result:

function getListOfCommits(gitCommitApiLink){
    return axios.get(gitCommitApiLink);
}

Then, in getAllPullRequests() it appears you are just doing one axios.get() call and then processing the results. That can be done like this:

function getAllPullRequests(gitPullRequestApiLink) {
    return axios.get(gitPullRequestApiLink).then(response => {
        let listOfPullRequestDataObjects = response.data;
        return listOfPullRequestDataObjects.map(item => {
            return item.number;
        });
    });
}

Now, if you're trying to execute these three operations sequentially in this order:

getAllPullRequests(someLink)
getCommitsForEachPullRequestNumber(listofprnumbers)
getListOfCommits(anotherlink)

You can chain the promises from those three operations together to sequence them:

 getAllPullRequests(someLink)
   .then(getCommitsForEachPullRequestNumber)
   .then(mapOfPullNumberToCommits => {
       // not entirely sure what you want to do here, perhaps
       // call getListOfCommits on each item in the map?
   }).catch(err => {
      console.log(err);
 });

Or, if you put this code in an async function, then you can use async/awit:

 async function getAllCommits(someLink) {
      let pullRequests = await getAllPullRequests(someLink);
      let mapOfPullNumberToCommits = await getCommitsForEachPullRequestNumber(pullRequests);
      // then use getlistOfCommits() somehow to process mapOfPullNumberToCommits
      return finalResults;
 }

 getAllCommits.then(finalResults => {
     console.log(finalResults);
 }).catch(err => {
     console.log(err);
 });
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 2
    @MohammadO - Did this help you? Did it answer your question? If so, you can indicate that to the community by clicking the checkmark to the left of the answer. If not, then please respond with a comment explaining what you're still confused about. When we help here, particularly when someone posts a fairly comprehensive answer, you should at least respond in some way. – jfriend00 Nov 24 '19 at 03:19
  • The ungrateful so-and-so just deleted [the question I answered](https://stackoverflow.com/q/59012727/3478010) without a word of acknowledgement let alone thanks. Hasn't got a clue. Deserves sanction, mine being to put him on my blacklist. – Roamer-1888 Nov 25 '19 at 15:46
  • I tried doing it this way but I'm still not getting how promises work and all. I tried doing it a different way by declaring a bunch of async functions, then making another function that invokves all the async ones. Contrary to what the fellow above said, I do greatly appreciate your help because it does clarify promises a bit more, but as I said, I'm new to Javascript and I'm still having a hard time grasping promises. I deleted the other question because it was an incredibly silly mistake I made (incrementing var x instead of y). –  Nov 25 '19 at 18:24
  • 1
    @MohammadO - Not sure what else I can say then. You didn't ask anything new. We can't really provide an entire tutorial on promises, so you will need to find some good reference material and do some reading/studying to help understand them better. For the functions you defined, this answer shows the general structure for how you sequence several asynchronous operations using promises which is exactly what the title of your question asks. I'm not sure what else you want to know on that topic. – jfriend00 Nov 25 '19 at 18:28
  • 1
    @MohammadO - You get help here through the goodwill of others. And, your actions will either encourage people to help you more or discourage people form helping you any more. Deleting that other question without even commenting at all to Roamer who tried to help you was just rude - no matter how silly your mistake was. That will send a message to stay away from your questions and you may just be wasting your time trying to help. In the future, please provide feedback (within hours) to those who try to help you. Otherwise, we will just assume you're being non-responsive and inconsiderate. – jfriend00 Nov 25 '19 at 18:30
  • @jfriend00, well said. Hopefully we will see a sea change in attitude. – Roamer-1888 Nov 25 '19 at 19:36
0

not as clean as jfriend00 solution, but I played with your code and it finally worked

https://repl.it/@gui3/githubApiPromises

you get the list of commits in the variable listOfCommits

I don't understand the purpose of your last function, so I dropped it

gui3
  • 1,711
  • 14
  • 30