1

All, I am struggling mightily with asynchronous callbacks in node.js. I am trying to make two HTTP requests and show the returned data from both at the end in separate variables. I am trying to use npm-async, but am failing setting it up properly.

Any help or suggestions would be greatly appreciated.

Here is my code:

// MODULES - INCLUDES
var xml2js = require('xml2js');
var parser = new xml2js.Parser();
var async = require('async');

// FORM - DATA COLLECTION
var cucmpub = 'xxxx';
var cucmversion = 'xxxx';
var username = 'xxxx';
var password = 'xxxx';

// JS - VARIABLE DEFINITION - GLOBAL
var authentication = username + ":" + password;
var soapreplyx = '';
var cssx = null;
var spacer = '-----';
var rmline1 = '';
var rmline2 = '';
var rmline3 = '';
var rmline4 = '';
var rmbottomup1 = '';
var rmbottomup2 = '';
var rmbottomup3 = '';
var soapreplyp = '';
var partitionsx = null;
var rmline1p = '';
var rmline2p = '';
var rmline3p = '';
var rmline4p = '';
var rmbottomup1p = '';
var rmbottomup2p = '';
var rmbottomup3p = '';

// HTTP.REQUEST - BUILD CALL - GLOBAL
var https = require("https");
var headers = {
    'SoapAction': 'CUCM:DB ver=' + cucmversion + ' listCss',
    'Authorization': 'Basic ' + new Buffer(authentication).toString('base64'),
    'Content-Type': 'text/xml; charset=utf-8'
};

// SOAP - AXL CALL - CSS
var soapBody = new Buffer('<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.5">' +
    '<soapenv:Header/>' +
    '<soapenv:Body>' +
    '<ns:listCss sequence="?">' +
    '<searchCriteria>' +
    '<name>%</name>' +
    '</searchCriteria>' +
    '<returnedTags uuid="?">' +
    '<name>?</name>' +
    '<description>?</description>' +
    '<clause>?</clause>' +
    '</returnedTags>' +
    '</ns:listCss>' +
    '</soapenv:Body>' +
    '</soapenv:Envelope>');

// SOAP - AXL CALL - PARTITIONS
var soapBody2 = new Buffer('<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.5">' +
    '<soapenv:Header/>' +
    '<soapenv:Body>' +
    '<ns:listRoutePartition sequence="?">' +
    '<searchCriteria>' +
    '<name>%</name>' +
    '</searchCriteria>' +
    '<returnedTags uuid="?">' +
    '<name>?</name>' +
    '</returnedTags>' +
    '</ns:listRoutePartition>' +
    '</soapenv:Body>' +
    '</soapenv:Envelope>');

// HTTP.REQUEST - OPTIONS - GLOBAL
var options = {
    host: cucmpub, // IP ADDRESS OF CUCM PUBLISHER
    port: 8443, // DEFAULT CISCO SSL PORT
    path: '/axl/', // AXL URL
    method: 'POST', // AXL REQUIREMENT OF POST
    headers: headers, // HEADER VAR
    rejectUnauthorized: false // REQUIRED TO ACCEPT SELF-SIGNED CERTS
};

// HTTP.REQUEST - GLOBAL (Doesn't seem to need this line, but it might be useful anyway for pooling?)
options.agent = new https.Agent(options);


async.series([
    function (callback) {

        // HTTP.REQUEST - OPEN SESSION - CSS
        var soapRequest = https.request(options, soapResponse => {
            soapResponse.setEncoding('utf8');
            soapResponse.on('data', chunk => {
                soapreplyx += chunk
            });
            // HTTP.REQUEST - RESULTS + RENDER
            soapResponse.on('end', () => {

                // EDIT - SCRUB XML OUTPUT
                var rmline1 = soapreplyx.replace(/<\?xml\sversion='1\.0'\sencoding='utf-8'\?>/g, '');
                var rmline2 = rmline1.replace(/<soapenv:Envelope\sxmlns:soapenv="http:\/\/schemas.xmlsoap.org\/soap\/envelope\/">/g, '');
                var rmline3 = rmline2.replace(/<soapenv:Body>/g, '');
                var rmline4 = rmline3.replace(/<ns:listCssResponse\sxmlns:ns="http:\/\/www\.cisco\.com\/AXL\/API\/[0-9]*\.[0-9]">/g, '');
                var rmbottomup1 = rmline4.replace(/<\/soapenv:Envelope>/g, '');
                var rmbottomup2 = rmbottomup1.replace(/<\/soapenv:Body>/g, '');
                var xmlscrubbed = rmbottomup2.replace(/<\/ns:listCssResponse>/g, '');
                // console.log(xmlscrubbed);
                // console.log(spacer);

                // XML2JS - TESTING
                parser.parseString(xmlscrubbed, function (err, result) {
                    var cssx = result['return']['css'];
                    callback(err, cssx);
                    // console.log(cssx);
                    // console.log(spacer);
                });
            });
        });

        // SOAP - SEND AXL CALL - CSS
        soapRequest.write(soapBody);
        soapRequest.end();
    },
    function (callback) {
        // SOAP - SEND AXL CALL - PARTITIONS
        var soapRequest2 = https.request(options, soapResponse2 => {
            soapResponse2.setEncoding('utf8');
            soapResponse2.on('data', chunk => {
                soapreplyp += chunk
            });
            // HTTP.REQUEST - RESULTS + RENDER
            soapResponse2.on('end', () => {
                console.log(soapreplyp);

                // EDIT - SCRUB XML OUTPUT
                var rmline1p = soapreplyp.replace(/<\?xml\sversion='1\.0'\sencoding='utf-8'\?>/g, '');
                var rmline2p = rmline1.replace(/<soapenv:Envelope\sxmlns:soapenv="http:\/\/schemas.xmlsoap.org\/soap\/envelope\/">/g, '');
                var rmline3p = rmline2.replace(/<soapenv:Body>/g, '');
                var rmline4p = rmline3.replace(/<ns:listRoutePartition\sxmlns:ns="http:\/\/www\.cisco\.com\/AXL\/API\/[0-9]*\.[0-9]">/g, '');
                var rmbottomup1p = rmline4.replace(/<\/soapenv:Envelope>/g, '');
                var rmbottomup2p = rmbottomup1.replace(/<\/soapenv:Body>/g, '');
                var xmlscrubbedp = rmbottomup2.replace(/<\/ns:listRoutePartition>/g, '');
                console.log(xmlscrubbedp);
                console.log(spacer);

                // XML2JS - TESTING
                parser.parseString(xmlscrubbedp, function (err, result) {
                    var partitionsx = result['return']['css'];
                    callback(err, partitionsx);
                    //   console.log(partitionsx);
                    //   console.log(spacer);
                });
            });
        });
        // SOAP - SEND AXL CALL - PARTITIONS
        soapRequest2.write(soapBody2);
        soapRequest2.end();
    },

    function (err, results) {
        console.log(cssx);
        console.log(partitionsx);
    }
]);

-----UPDATE 1-----

Ok, I've updated my code; and I think I've got it! Thank you to the both of you for helping me through this! Alright, so this isn't exactly what you recommended @Chris Phillips. I was hoping to be able to display results separately instead of all combined. I looked up chaining Promises together and found this article: How to chain and share prior results with Promises. That mentioned how to "Nest, so all Previous Results Can Be Accessed". That did the trick for me.

Also, once I got my head wrapped around the request-promise framework, that was really easy and looks a lot better than the regular http.request framework.

Here is the new code!

// MODULES - INCLUDES
var xml2js = require('xml2js');
var parser = new xml2js.Parser();
var rp = require('request-promise');

// FORM - DATA COLLECTION
var cucmpub = 'xxxx';
var cucmversion = 'xxxx';
var username = 'xxxx';
var password = 'xxxx';

// JS - VARIABLE DEFINITION - GLOBAL
var authentication = username + ":" + password;
var cssx = null;
var partitionsx = null;
var spacer = '-----';

// CSS - JS - VARIABLE DEFINITION
var cssrmline1 = '';
var cssrmline2 = '';
var cssrmline3 = '';
var cssrmline4 = '';
var cssrmbottomup1 = '';
var cssrmbottomup2 = '';

// CSS - SOAP - AXL REQUEST
var cssaxlrequest = new Buffer('<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.5">' +
    '<soapenv:Header/>' +
    '<soapenv:Body>' +
    '<ns:listCss sequence="?">' +
    '<searchCriteria>' +
    '<name>%</name>' +
    '</searchCriteria>' +
    '<returnedTags uuid="?">' +
    '<name>?</name>' +
    '<description>?</description>' +
    '<clause>?</clause>' +
    '</returnedTags>' +
    '</ns:listCss>' +
    '</soapenv:Body>' +
    '</soapenv:Envelope>');

// CSS - HTTP - REQUEST BUILD
var csshttprequest = {
    method: 'POST',
    uri: 'https://' + cucmpub + ':8443/axl/',
    rejectUnauthorized: false,
    headers: {
        'SoapAction': 'CUCM:DB ver=' + cucmversion + ' listCss',
        'Authorization': 'Basic ' + new Buffer(authentication).toString('base64'),
        'Content-Type': 'text/xml; charset=utf-8',
    },
    body: cssaxlrequest,
};

// PARTITIONS - JS - VARIABLE DEFINITION
var partitionsrmline1 = '';
var partitionsrmline2 = '';
var partitionsrmline3 = '';
var partitionsrmline4 = '';
var partitionsrmbottomup1 = '';

// PARTITIONS - SOAP - AXL REQUEST
var partitionsaxlrequest = new Buffer('<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://www.cisco.com/AXL/API/11.5">' +
    '<soapenv:Header/>' +
    '<soapenv:Body>' +
    '<ns:listRoutePartition sequence="?">' +
    '<searchCriteria>' +
    '<name>%</name>' +
    '</searchCriteria>' +
    '<returnedTags uuid="?">' +
    '<name>?</name>' +
    '</returnedTags>' +
    '</ns:listRoutePartition>' +
    '</soapenv:Body>' +
    '</soapenv:Envelope>');

// PARTITIONS - HTTP - REQUEST BUILD
var partitionshttprequest = {
    method: 'POST',
    uri: 'https://' + cucmpub + ':8443/axl/',
    rejectUnauthorized: false,
    headers: {
        'SoapAction': 'CUCM:DB ver=' + cucmversion + ' listRoutePartition',
        'Authorization': 'Basic ' + new Buffer(authentication).toString('base64'),
        'Content-Type': 'text/xml; charset=utf-8',
    },
    body: partitionsaxlrequest,
};

// CHAINED REQUESTS + OUTPUT
rp(csshttprequest)
    .then(function (resultcss) {
        var cssrmline1 = resultcss.replace(/<\?xml\sversion='1\.0'\sencoding='utf-8'\?>/g, '');
        var cssrmline2 = cssrmline1.replace(/<soapenv:Envelope\sxmlns:soapenv="http:\/\/schemas.xmlsoap.org\/soap\/envelope\/">/g, '');
        var cssrmline3 = cssrmline2.replace(/<soapenv:Body>/g, '');
        var cssrmline4 = cssrmline3.replace(/<ns:listCssResponse\sxmlns:ns="http:\/\/www\.cisco\.com\/AXL\/API\/[0-9]*\.[0-9]">/g, '');
        var cssrmbottomup1 = cssrmline4.replace(/<\/soapenv:Envelope>/g, '');
        var cssrmbottomup2 = cssrmbottomup1.replace(/<\/soapenv:Body>/g, '');
        var cssxmlscrubbed = cssrmbottomup2.replace(/<\/ns:listCssResponse>/g, '');
        parser.parseString(cssxmlscrubbed, function (err, result) {
            var cssx = result['return']['css'];
            // console.log(cssx);
            // console.log(spacer);
            return rp(partitionshttprequest)
                .then(function (resultpartitions) {
                    var partitionsrmline1 = resultpartitions.replace(/<\?xml\sversion='1\.0'\sencoding='utf-8'\?>/g, '');
                    var partitionsrmline2 = partitionsrmline1.replace(/<soapenv:Envelope\sxmlns:soapenv="http:\/\/schemas.xmlsoap.org\/soap\/envelope\/">/g, '');
                    var partitionsrmline3 = partitionsrmline2.replace(/<soapenv:Body>/g, '');
                    var partitionsrmline4 = partitionsrmline3.replace(/<ns:listRoutePartitionResponse\sxmlns:ns="http:\/\/www\.cisco\.com\/AXL\/API\/[0-9]*\.[0-9]">/g, '');
                    var partitionsrmbottomup1 = partitionsrmline4.replace(/<\/soapenv:Envelope>/g, '');
                    var partitionsrmbottomup2 = partitionsrmbottomup1.replace(/<\/soapenv:Body>/g, '');
                    var partitionsxmlscrubbed = partitionsrmbottomup2.replace(/<\/ns:listRoutePartitionResponse>/g, '');
                    parser.parseString(partitionsxmlscrubbed, function (err, result) {
                        var partitionsx = result['return']['routePartition'];
                        console.log(cssx);
                        console.log(spacer);
                        console.log(partitionsx);
                    });
                })
        });
    })
Andrew Petersen
  • 163
  • 1
  • 9
  • You may want to use `async`/`await` if possible, as Promise-driven concurrent code is often significantly easier to use. – tadman Nov 15 '17 at 20:41
  • tadman, Thanks for your response. However, I believe async/await is only available in node v8.x or higher right? If so, I'm stuck with node v7.x right now. – Andrew Petersen Nov 16 '17 at 16:37
  • You can use regular Promises in pretty much any version of Node and use the `then()` chaining. That's a lot easier than what you have here, plus if and when you move to Node v8 you can flip `then()` to `await` and simplify your code even more. – tadman Nov 16 '17 at 16:45

1 Answers1

3

Well you can use the request-promise library to make the http calls instead so it would be come something like (a bit rough, error handling omitted)

const request = require("request-promise");

const options1 = {
 //.. set URL, headers etc
};

request(options1).then ( body => {

   // do processing for request 1
   return [results1];
}).then( results => {
    return request(options2).then ( body => {
      //do processing for request 2
      results.push(results2);
      return results;
   });

})
.then((results) => {
});

If you are using Node 8.x or greater you can use async/await to do something like

let resp1 = await request(options1);

//process resp1

let resp2 = await request(options2);

// process resp2

let results = [resp1, resp2];

I would recommend moving all your request making code into a function so you can more easily re-use it.

Chris Phillips
  • 673
  • 5
  • 10
  • 1
    Chris, thanks for your response. I really appreciate it! Ok, so I'm working on changing my code into the request-promise format. I'll post an edit to my question when I have that finished. If I am understanding your answer correctly though, this would be putting the results from the first request together with the results from the second request? Also, I'm afraid I'm stuck with node 7.x for right now. Upgrade planned for much later. – Andrew Petersen Nov 16 '17 at 16:36
  • @AndrewPetersen Yes that is correct. Let me know how it turns out. – Chris Phillips Nov 16 '17 at 16:38
  • Alright! I've got my code updated. Thanks to you both for steering me in the right direction. I think I've got to rock and role. – Andrew Petersen Nov 17 '17 at 19:23
  • @AndrewPetersen Great! glad to hear it. I would recommend extracting all the code you use to scrub the SOAP responses into its own function so you can reduce the duplication of code. – Chris Phillips Nov 17 '17 at 19:37
  • O good thought. I'll work through that. Thanks again for the help! – Andrew Petersen Nov 17 '17 at 22:16