BenWS
BenWS

Reputation: 531

Callbacks within http request methods - not happening in correct order

I've written a program that makes an HTTP GET request for three distinct URLs. The program is supposed to output the message body in the order the URLs are provided, however it's not doing so even though I'm making callbacks in exactly that order.

The final program is supposed to require the user to input the URLs via command line, however I've simply made variable assignments for ease of testing.

I realize this code could be more object-oriented - however I'm new to JavaScript and it's not my focus to learn how at the moment

var http = require('http')

// var url_1 = process.argv[2]
// var url_2 = process.argv[3]
// var url_3 = process.argv[4]

var url_1 = 'http://youvegotmail.warnerbros.com/cmp/0frameset.html'
var url_2 = 'http://www.3riversstadium.com/index2.html'
var url_3 = 'http://toastytech.com/evil/'

var output_1 = ''
var output_2 = ''
var output_3 = ''


function getHttp_1 (callback) {
    http.get(url_1, function getResponse (response1) {
        response1.setEncoding('utf8')
        response1.on('data', function (data) {
            output_1 = output_1 + data
        })
        response1.on('end', function processData() {
            console.log("Printing Result 1:")
            callback(output_1)
        })
    })
}

function getHttp_2 (callback) {
    http.get(url_2, function getResponse (response2) {
        response2.setEncoding('utf8')
        response2.on('data', function (data) {
            output_2 = output_2 + data
        })
        response2.on('end', function processData() {
            console.log("Printing Result 2:")
            callback(output_2)
        })
    })
}

function getHttp_3 (callback) {
    http.get(url_3, function getResponse (response3) {
        response3.setEncoding('utf8')
        response3.on('data', function (data) {
            output_3 = output_3 + data
        })
        response3.on('end', function processData() {
            console.log("Printing Result 3:")
            callback(output_3)
        })
    })
}

function printResults(output) {
    console.log("Result")
    // console.log(output)
}

getHttp_1(printResults)
getHttp_2(printResults)
getHttp_3(printResults)

EDIT:

Results I'm generally getting:

Printing Result 3:
Result
Printing Result 2:
Result
Printing Result 1:
Result

Results I'm expecting:

Printing Result 1:
Result
Printing Result 2:
Result
Printing Result 3:
Result

Upvotes: 1

Views: 248

Answers (4)

Noah Freitas
Noah Freitas

Reputation: 17430

In contrast to the sequential callback approach proposed by some answers, using Promises will make this both more efficient (the requests will be made in parallel) and simpler:

var http = require('http'),
    urls = [
        'http://youvegotmail.warnerbros.com/cmp/0frameset.html',
        'http://www.3riversstadium.com/index2.html',
        'http://toastytech.com/evil/'
    ];

Promise.all(urls.map(getUrl))
       .then(function (results) {
            results.forEach(function (output, i) {
                console.log("Result #" + (i + 1) +
                            " with length: " + output.length);
            });
       });

function getUrl(url, i) {
    return new Promise(function (resolve, reject) {
        http.get(url, function getResponse(resp) {
            var output = '';
            resp.setEncoding('utf8');
            resp.on('data', function (data) {
                output += data;
            });
            resp.on('end', function processData() {
                console.log("Resolving Result " + (i + 1) + ":");
                resolve(output);
            });
        })
    });
}

Upvotes: 3

Jacob
Jacob

Reputation: 78900

The async module can really help for controlling how async tasks are executed. For example, if you want your requests to happen one after the other:

async.series([
  function (next) { makeRequest(url_1, next); },
  function (next) { makeRequest(url_2, next); },
  function (next) { makeRequest(url_3, next); },
], function (err, result) {
   // All done
});

// Or you can get fancy
//async.series([
//  makeRequest.bind(null, url_1),
//  makeRequest.bind(null, url_2),
//  makeRequest.bind(null, url_3),
//]);

function makeRequest(url, callback) {
  http.get(url, function getResponse (res) {
      var output = '';
      res.setEncoding('utf8')
      res.on('data', function (data) {
          output += data
      })
      response1.on('end', function processData() {
          callback(output)
      })
  })
}

If you don't care what order they occur in but want to output them in order:

async.parallel([
  function (next) { makeRequest(url_1, next); },
  function (next) { makeRequest(url_2, next); },
  function (next) { makeRequest(url_3, next); },
], function (err, results) {
  if (err) {
    return void console.error('Got an error:', err.stack);
  }

  console.log(results); // Will output array of every result in order
});

If the requests are dependent on each other, async.auto is useful to tie the result of one request to the request of another.

Upvotes: 2

Sami
Sami

Reputation: 3800

JavaScript/AJAX calls are async so don't follow the order you call them. To call them in sequence/specific order, do like:

$(function () {

//setup an array of AJAX options, each object is an index that will specify information for a single AJAX request
var ajaxes  = [{ url : '<url>', dataType : 'json' }, { url : '<url2>', dataType : 'utf8' }],
    current = 0;

//declare your function to run AJAX requests
function do_ajax() {

    //check to make sure there are more requests to make
    if (current < ajaxes.length) {

        //make the AJAX request with the given data from the `ajaxes` array of objects
        $.ajax({
            url      : ajaxes[current].url,
            dataType : ajaxes[current].dataType,
            success  : function (serverResponse) {
                ...
                //increment the `current` counter and recursively call this function again
                current++;
                do_ajax();
            }
        });
    }
}

//run the AJAX function for the first time once `document.ready` fires
do_ajax();
});

Another option could be:

function callA() {
$.ajax({
...
success: function() {
  //do stuff
  callB();
}
});
}

function callB() {
    $.ajax({
    ...
    success: function() {
        //do stuff
        callC();
    }
    });
}

function callC() {
    $.ajax({
    ...
    });
}


callA();

Ref: Multiple Calls in Order

Upvotes: 0

Jack Ryan
Jack Ryan

Reputation: 1318

Welcome to the asynchronous life of node.js! As you fire off those HTTP requests, one will not wait for the request before it to finish before it fires. You are seeing this odd behavior because you are practically sending all 3 requests at once, and simply printing as you see the responses.

Edit: If you do want to see them in correct order, fire off the second HTTP request inside the callback of the first, and then the third inside the callback of the second. That guarantees you won't get the data until after each one before it finishes.

function getHttp_1 (callback) {
    http.get(url_1, function getResponse (response1) {
        response1.setEncoding('utf8')
        response1.on('data', function (data) {
            output_1 = output_1 + data
        })
        response1.on('end', function processData() {
            console.log("Printing Result 1:")
            callback(output_1)
            getHttp_2(callback)
        })
    })
}

Upvotes: 2

Related Questions