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);
                    });
                })
        });
    })
3
  • You may want to use async/await if possible, as Promise-driven concurrent code is often significantly easier to use. Commented Nov 15, 2017 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. Commented Nov 16, 2017 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. Commented Nov 16, 2017 at 16:45

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

5 Comments

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.
@AndrewPetersen Yes that is correct. Let me know how it turns out.
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.
@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.
O good thought. I'll work through that. Thanks again for the help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.