0

I am trying to get a bunch of ID's from an API and then form a sequence of requests which would make further calls to an API to fetch some parameters. These would be totaled and i expect the output results to be pushed as JSON array.

The problem is REST call is async and i've put a promise but not sure when to resolve the promise back to the calling function, the rest call some times take a second or 2 to respond back.

I would like know at what point can i resolve the promise or how to know when the totals have been computed ?

The Route

app.get("/sonar/:x_id",function(req,resp) { 

getRestSonar(req.params.x_id).then(function (fromResolve) {
  resp.send(fromResolve);
});


});

The function with promise which makes the rest call loops

var getRestSonar = function(requestX) {


return new Promise(function(resolve,reject) {

    var unirest = require("unirest");
    var reqx = unirest("GET", "http://sonarqubexxServer/api/projects");

    var outputJson = {
      table: []
    };


    reqx.end(function (res) {
          if (res.error) throw new Error(res.error);

     // console.log(res.body);
            var result = res.body;
            //var needle = req.params.csi_id;
            var needle = requestX;
            var TotalDuplicateLines = 0; 
            var TotalBugs = 0;
            var TotalNcloc = 0;
            var TotalCodeSmells = 0;
            var TotalVulnerabilities = 0;


            for (var i=0;i<result.length;i++) {
                if (result[i].nm.indexOf(needle) !== -1) {
                    console.log(result[i].k);
                    var queryUrl = "http://sonarqubexxServer/api/resources?resource="+result[i].k+"&metrics=code_smells,bugs,vulnerabilities,ncloc,coverage,duplicated_lines&format=json"
                    console.log(queryUrl);
                    var subrequest = unirest("GET",queryUrl);

                          subrequest.end(function (resXX) {
                               if (resXX.error);

                               var resXXResult = resXX.body;

                               for (var i=0;i<resXXResult.length;i++) {
              // var duplicateData = resXXResult[0].msr.filter(item => item.key == 'duplicated_lines');
                                            resXXResult[i].msr.forEach(m => {
                                                  if (m.key === 'duplicated_lines') {
                                                      console.log('Duplicated Lines ' + m.val);
                                                      TotalDuplicateLines += m.val;



                                                  }
                                                  else if(m.key === 'bugs' ) {
                                                      console.log('Bugs ' + m.val);
                                                      TotalBugs += m.val;

                                                  }
                                                  else if(m.key === 'ncloc' ) {
                                                      console.log('Lines of Code ' + m.val);
                                                      TotalNcloc += m.val;

                                                  }
                                                  else if(m.key === 'code_smells' ) {
                                                      console.log('Code Smells ' + m.val);
                                                      TotalCodeSmells += m.val;

                                                  }
                                                  else if(m.key === 'vulnerabilities' ) {
                                                      console.log('Vulnerabilities ' + m.val);
                                                      TotalVulnerabilities += m.val;

                                                      outputJson.table.push({totduplines:TotalDuplicateLines},{totVul:TotalVulnerabilities});

                                                  }


                                      });


                             console.log("Iam here with I :: " + i);
                             if (i === (resXXResult.length - 1)) {
                               //Should i resolve here makes no sense
                               console.log("Resolved the promise now..");

                             }

                             //The for ends here
                             }
// I see this is a bad place to resolve..
                    resolve(outputJson);


                         });


            }
      }



    });


});

}

EDIT : As suggested in the comments, split the calls into smaller sections

Now, i fetch the api calls seperatly create an array out of it, then use promises to call back to the API ? how do i resolve each call by looping over it ? When i try to loop it always resolves request[0] and then comes out of the promise, how can i create a promise array and wait for them to complete ?

app.get("/sonar/:csi_id",function(req,resp) { 

var collectiveResult = [];

getRestSonar(req.params.csi_id).then(function (fromResolve) {

return splitReqUrl(fromResolve);

}).then(function(fromSplitUrl) {

  console.log("I am from split url ::::" + fromSplitUrl);

return getSubSonarProperties(fromSplitUrl);

}).then(function(fromsubSonar) {

collectiveResult.push(fromsubSonar);
console.log("+++++++++++++++++++++++++++");
console.log(fromsubSonar);
 resp.send(collectiveResult);

});





});



var getSubSonarProperties = function(getUrl) {

  return new Promise(function(resolve,reject) {

  var getSubRest = require("unirest");
  console.log("Attempting to GET " + getUrl);
  var req = getSubRest("GET",getUrl);
  var outputJson = {
      table: []
  }

            var TotalDuplicateLines = 0; 
            var TotalBugs = 0;
            var TotalNcloc = 0;
            var TotalCodeSmells = 0;
            var TotalVulnerabilities = 0;

  req.end(function (res) {
    if (res.error);
    var resXXResult = res.body;

    resolve(resXXResult);

  });


});

}


var splitReqUrl = function(request) {

  return new Promise(function(resolve,reject) {
  resolve(request[1]);

   //for(var i=0; i< request.length; i++) {
    // resolve(request[i]);
   //}

  });


}




var getRestSonar = function(requestX) {


return new Promise(function(resolve,reject) {

    var unirest = require("unirest");
    var reqx = unirest("GET", "http://sonarqubexxx/api/projects");

    var outputJson = {
      table: []
    };

    reqx.end(function (res) {
          if (res.error) throw new Error(res.error);

     // console.log(res.body);
            var result = res.body;
            //var needle = req.params.csi_id;
            var needle = requestX;


            var queryArray = [];



            for (var i=0;i<result.length;i++) {
                if (result[i].nm.indexOf(needle) !== -1) {
                    console.log(result[i].k);
                    var queryUrl = "http://sonarxxx/api/resources?resource="+result[i].k+"&metrics=code_smells,bugs,vulnerabilities,ncloc,coverage,duplicated_lines&format=json"
                    //console.log(queryUrl); 
                    queryArray.push(queryUrl);    
                }
                  if (i === (result.length - 1)) {
                    resolve(queryArray);
                  }
            }
    });
});
}
Sudheej
  • 1,873
  • 6
  • 30
  • 57
  • are you calling resolve in a loop? It's hard to tell with your "lots of white space" code formatting - ahh, I see you recognise the fact that resolving there is not correct - so, yeah, you need to rethink the code I think – Jaromanda X Feb 25 '18 at 03:30
  • 3
    Perhaps you should try to break up your monolith into smaller functions that are focused on achieving a single goal. If you cannot figure out the data/logic flow of something, it is generally time to refactor. Please accept this as constructive criticism – Randy Casburn Feb 25 '18 at 04:05
  • have you tried making your for loop an array of promises that you can call with promise.all https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all or http://bluebirdjs.com/docs/api/promise.all.html the .then(arrayResponseData)=>{} should have all the data you need – Jim Factor Feb 25 '18 at 04:06
  • Thanks for the feedback and suggestions – Sudheej Feb 25 '18 at 05:13
  • @RandyCasburn I've splitted and added sequential promises, but unable to loop the requests, can you suggest a way of doing this ? – Sudheej Feb 25 '18 at 17:43
  • 1
    great! So now investigate PromiseAll() as Jim Factor has suggested. PromiseAll() will resolve _once_ after all promises have resolved or will reject if any single promise rejects. – Randy Casburn Feb 25 '18 at 17:46
  • 1
    As I understand it, `unirest` has several forks, at least one of which offers `unirest(...).exec()`, which returns a Promise. If the fork/version you are using does not offer `.exec()`, then you will need to "promisify" `unirest`. This is by far the most important aspect of the refactoring you have been advised to undertake. – Roamer-1888 Feb 26 '18 at 00:04

1 Answers1

2

Problem

First of all the problem with your solution is that you're trying to make everything inside a single big new Promise(...) creator.

Even if you manage to make that work it's still a common anti-pattern as Promises are made to be chained using the .then(...) method.

As pointed out by Roamer-1888 there oughta be a fork of unirest that handles Promises directly instead of requiring callbacks as in your example, but let's stick with your version of unirest here.

Solution

So what you need to be doing is create a Promise chain to handle the different steps of your code and pass the results down the chain.

Your steps seem to be:

  1. Make the first call to retrieve initial results.
  2. Filter the results based on the requestX input.
  3. For each item left, make several calls to obtain more data.
  4. Put everything back into an outputJson object.

Basically the only async steps are 1 and 3, but it might be ok to add a third step to build your outputJson and pass it downstream.

So let's start with the first step.

1. Make the first call

In the first link of the Promise chain we need to retrieve the initial results with your first unirest call:

new Promise((resolve, reject) => {
    unirest("GET", "http://sonarqubexxServer/api/projects")
        .end((res) => {
            if (res.error) {
                reject(res.error);
            } else {
                resolve(res.body);
            }
        });
})

See in this example I already checked if the response contains an error and fired a rejection in that case, otherwise I resolve the promise with the body (the data we need).

The Promise we created above will throw an error if the request fails, and will downstream the body of the response if everything goes fine.

2. Filtering and Sub-calls

Now then we can go ahead and use the full potential of Promises with the .then(...) method:

new Promise((resolve, reject) => {
    unirest("GET", "http://sonarqubexxServer/api/projects")
        .end((res) => {
            if (res.error) {
                reject(res.error);
            } else {
                resolve(res.body);
            }
        });
}).then((results) => {
    results = results.filter((result) => {
        return result.nm.indexOf(request) != -1;
    });
    return Promise.all(results.map((result) => {
        return new Promise((resolve, reject) => {
            var queryUrl = "http://sonarqubexxServer/api/resources?resource=" + result.k + "&metrics=code_smells,bugs,vulnerabilities,ncloc,coverage,duplicated_lines&format=json"
            unirest("GET", queryUrl)
                .end((res) => {
                    if (res.error) {
                        reject(res.error);
                    } else {
                        resolve(res.body);
                    }
                });
        })
    }))
})

In this step I used some Array methods to make the code cleaner and Promise.all to handle several promises together.

Array.filter is a method which iterates an array and checks for each item if it should be kept in the filtered output or not. So, in your case, we want to keep only those items where result.nm.indexOf(request) != -1.

Array.map is a method which iterates an array and converts each item to something else. Basically the function you provide takes each item as input, converts it to something else and then replaces this new value to the old one in the output array.

Finally Promise.all accepts an array of Promises and returns a Promise itself. This returned Promise will resolve when all the given Promises resolve and will pass downstream an array which items are the results of each single Promise.

So by writing Promise.all(results.map((results) => { return new Promise(...) })) we convert each result in the results array into a Promise that executes the result-specific call and put it into the output array of Promises which is fed to Promise.all so they get executed at once.

3. Build the outputJSON

Now the Promise chain outputs the result of Promise.all which is an array of all the results of each Promise, which are the results of each sub-call.

We can then simply take the downstream data and use your nested iterations to build the outputJSON to be passed downstream:

new Promise((resolve, reject) => {
    unirest("GET", "http://sonarqubexxServer/api/projects")
        .end((res) => {
            if (res.error) {
                reject(res.error);
            } else {
                resolve(res.body);
            }
        });
}).then((results) => {
    results = results.filter((result) => {
        return result.nm.indexOf(request) != -1;
    });
    return Promise.all(results.map((result) => {
        return new Promise((resolve, reject) => {
            var queryUrl = "http://sonarqubexxServer/api/resources?resource=" + result.k + "&metrics=code_smells,bugs,vulnerabilities,ncloc,coverage,duplicated_lines&format=json"
            unirest("GET", queryUrl)
                .end((res) => {
                    if (res.error) {
                        reject(res.error);
                    } else {
                        resolve(res.body);
                    }
                });
        })
    }))
}).then((allResults) => {
    var TotalDuplicateLines = 0;
    var TotalBugs = 0;
    var TotalNcloc = 0;
    var TotalCodeSmells = 0;
    var TotalVulnerabilities = 0;
    var outputJson = {
        table: []
    };

    for (var i = 0; i < allResults; i++) {
        for (var j = 0; j < allResults[i].length; j++) {
            allResults[i][j].msr.forEach(m => {
                if (m.key === 'duplicated_lines') {
                    TotalDuplicateLines += m.val;
                }
                else if (m.key === 'bugs') {
                    TotalBugs += m.val;
                }
                else if (m.key === 'ncloc') {
                    TotalNcloc += m.val;
                }
                else if (m.key === 'code_smells') {
                    TotalCodeSmells += m.val;
                }
                else if (m.key === 'vulnerabilities') {
                    TotalVulnerabilities += m.val;
                    outputJson.table.push({ totduplines: TotalDuplicateLines }, { totVul: TotalVulnerabilities });
                }
            });
        }
    }
    return outputJson;
})

If your return this long Promise chain in your getRestSonar(request) function, then you could write getRestSonar(request).then((outputJson) => { ... do something with your outputJson ... })

Carlo Moretti
  • 2,213
  • 2
  • 27
  • 41