Make a blocking call to a function in Node.js required in this case?

I am starting to learn node.js. I am stuck with a problem here. I am calling a weather service which returns a JSON(url below).

http://api.wunderground.com/api/Your_key/conditions/q/CA/San_Francisco.json

I want to display the results from the api on an HTML page.

I have written the following code(getInfo module) to retrieve and return the JSON.

var fbResponse; 

module.exports = function (url) {

var http=require('http');


http.get(url, function(res) {
    var body = '';

    res.on('data', function(chunk) {
        body += chunk;
    });

    res.on('end', function() {
        fbResponse = JSON.parse(body);  
        console.log("Got response: ", fbResponse.response);


    });

}).on('error', function(e) {
      console.log("Got error: ", e);
});


return fbResponse;
};

So to use this module I created a test.js file as follows:

var relay = require('./getInfo');
var url = 'http://api.wunderground.com/api/Your_key/conditions/q/CA/San_Francisco.json';
var response;
var x=relay(url);

console.log (x);

Output is as follows:

undefined                //  console.log(x) from test.js
Got response:  { version: '0.1',
  termsofService: 'http://www.wunderground.com/weather/api/d/terms.html',
  features: { conditions: 1 } }

The console output in the test code runs first with no data in it. The HTTP get competes later and displays the actual output I need.

How can I modify the test code to make a blocking call such that var x in the test code actually have the JSON output instead of undefined?

Can I achieve the desired result without a blocking call to the getInfo module?

Answers:

Answer

As you know, node is asynchronous, so the callback of http.get and res.on('end', .. will fire after relay function is executed and it is returned. So normally you can't return the result from it.

You have a couple of choices:

  • Pass a callback to relay and use that:

    module.exports = function (url, cb) {
        http.get(url, function(res) {
    
            var body = '';
            res.on('data', function(chunk) {
                body += chunk;
            });
    
            res.on('end', function() {
                cb(null, JSON.parse(body));
            });
    
        }).on('error', cb);
    };
    

    Then use it like this:

    var relay = require('./getInfo');
    relay(url, function (err, x) {
        if (err) {
            console.error('oohh i got a error: ', err)    
        }
        console.log('oohh i got a response: ', x)    
    });
    
  • Use promises. This is almost same as passing callbacks. A little less lightweight, but when combining different asynchronous operations, you will understand how awesome they are. For just one asynchronous call there might not be any difference. Here I use q. You can also use bluebird which is way more performant but lacks some of the sugar of q. You can read this article to understand why promises are cleaner than callbacks in some cases.

    module.exports = function (url) {
        var deferred = Q.defer();
        http.get(url, function(res) {
            var body = '';
            res.on('data', function(chunk) {
                body += chunk;
            });
    
            res.on('end', function() {
                deferred.resolve(JSON.parse(body));
            });
    
        }).on('error', function(e) {
            deferred.reject(e);
        });
        return deferred.promise;
    };
    
    
    
    var relay = require('./getInfo');
    relay(url).then(function responseHandler(x) {
        console.log('my awesome response')
    }, function errorHandler(err) {
        console.error('got an error', err);
    });
    
  • Use generators. It is part of Ecmascript 6 specification it only exists in node v0.11.x and later. But it would be almost what you want.

    With that past promise example we can do this:

    Q.async(function *() {
        var relay = require('./getInfo');
        var x = yield relay(url);
        console.log('my awesome response', x)
    });
    

    This is almost what you want. You can also achieve it using the callback solution with co library:

    co(function *() {
        var relay = require('./getInfo');
        var x = yield relay.bind(null, url);
        console.log('my awesome response', x);
    });
    

    You can also use node-fibers in above example which is almost a similar tool like generators.

    If you want to use bleeding edge Javascript, you can use Async/Await instead of generators with promises.

Answer

You need to pass in a callback instead:

var http = require('http');

module.exports = function(url, cb) {
    http.get(url, function(res) {
        var body = '';

        res.on('data', function(chunk) {
            body += chunk;
        });

        res.on('end', function() {
            var resp, err;
            try {
              resp = JSON.parse(body);
            } catch (ex) {
              err = ex;
            }
            cb(err, resp);
        });

    }).on('error', function(e) {
          console.log("Got error: ", e);
          cb(e);
    });
};

Then use it like:

var relay = require('./getInfo');
var url = 'http://api.wunderground.com/api/Your_key/conditions/q/CA/San_Francisco.json';
var response;
relay(url, function(err, resp) {
  if (err) throw err; // TODO: handle better
  console.dir(resp);
});
Answer

you can take a callback in your module function to return the result.

module.exports = function (url, onsuccess) {
    ...

    res.on('end', function() {
        fbResponse = JSON.parse(body);
        if(onsuccess){
            onsuccess(null, fbResponse);
        }

Then in your caller code:

relay(url, function(err, result){
    console.log(result);
});

Another option is to use httpsync module which provides synchronous apis for the same functionality that 'http' module provides. But in node js programming, you should always avoid synchronous calls.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.