0

Objective

Disclaimer: I am new to node world and having tough time wrapping head around node asynchronous behaviour.

I am trying to write a wrapper function to do a https.get on a given url and return json output.

Code

const https = require('https');

// Get the user details
var myUrl = <valid-url>;

const getJson = function(url) {
  // https get request
  const req = https.get(url, (res) => {
    // get the status code
    const { statusCode } = res;
    const contentType = res.headers['content-type'];

    // check for the errors
    let error;
    if (statusCode !== 200) {
      error = new Error('Request Failed.\n' +
                        `Status Code: ${statusCode}`);
    } else if (!/^application\/json/.test(contentType)) {
      error = new Error('Invalid content-type.\n' +
                        `Expected application/json but received ${contentType}`);
    }
    if (error) {
      console.error(error.message);
      // consume response data to free up memory
      res.resume();
      return;
    }

    //parse json
    res.setEncoding('utf8');
    let rawData = '';
    res.on('data', (chunk) => { rawData += chunk; });
    res.on('end', () => {
      try {
        const parsedData = JSON.parse(rawData);
        console.log(parsedData);
      } catch (e) {
        console.error(e.message);
      }
    });
  }).on('error', (e) => {
    console.error(`Got error: ${e.message}`);
  });
}

console.log(getJson(myUrl));

Output

undefined
{ user_id: <user-id>,
  name: 'Ajay Krishna Teja',
  email: <my-email> }

Issue

So the https.get is able to hit end point and get data but not able to return the json. Constantly returning Undefined.

Things I tried

  1. Returning parsedData on res.on(end) block
  2. Defining a var and copying parsedData
  3. Copying to a global variable (although I knew it's very bad practice)

Places I looked up

  1. Node.js variable declaration and scope
  2. How to get data out of a Node.js http get request
  3. Javascript function returning undefined value in node js

Updated: Working code

const getJson = function(url,callback) {
  // https get request
  const req = https.get(url, (res) => {
    // get the status code
    const { statusCode } = res;
    const contentType = res.headers['content-type'];

    // check for the errors
    let error;
    if (statusCode !== 200) {
      error = new Error('Request Failed.\n' +
                        `Status Code: ${statusCode}`);
    } else if (!/^application\/json/.test(contentType)) {
      error = new Error('Invalid content-type.\n' +
                        `Expected application/json but received ${contentType}`);
    }
    if (error) {
      console.error(error.message);
      // consume response data to free up memory
      res.resume();
      return;
    }

    //parse json
    res.setEncoding('utf8');
    let rawData = '';
    res.on('data', (chunk) => { rawData += chunk; });
    res.on('end', () => {
      try {
        const parsedData = JSON.parse(rawData);
        callback(parsedData);
      } catch (e) {
        callback(false);
        console.error(e.message);
      }
    });
  }).on('error', (e) => {
    console.error(`Got error: ${e.message}`);
  });

  return req;
}

// calling
getJson(amznProfileURL,(res) => {
  console.log(res);
});
PseudoAj
  • 3,634
  • 2
  • 15
  • 32

4 Answers4

1

Short answer: You are not returning anything in your getJson function and undefined is the default Node/Javascript return value.

function getJson(){
  callAsyncFunction(param1, param2, param3)
  // there is no return value!
}

Longer answer: Javascript (and Node as a result) is a single threaded language that uses callbacks as it's mechanism to return async results back to the callee. To do this, you pass a function into asynchronous functions as a parameter and then that function gets called at some point in the future whenever the asynchronous function is ready to send back it's result. Calling return from this "anonymous function" is actually just returning from the "callback" function you are sending into the async function.

function getJson(){
  console.log('A')
  // request is started, but getJson continues execution!
  http.get(url, (res)=> {
    console.log('C') // by the time I'm called, 'B' has already been printed and the function has returned!
    return true // this won't return getJson! It will only return the callback function which doesn't do anything!
  })
  console.log('B')
  // end of function without return value, return undefined!
}

// Will print 'A', 'B', 'C'

There are a couple different ways you can handle this. Callbacks have been used traditionally but Javascript also natively supports Promises which are a little easier to manage and are used in many popular frameworks by default.

You can implement your function with callbacks by providing your own callback parameter to call as soon as http.get returns itself.

// define getJson with second callback parameter
const getJson = function(url, callback) {
  http.get(url, (res) => {
    if(res){
      callback(res) // result came back, send to your own callback function
    } else {
      callback(false) // request failed, send back false to signify failure
    }
  })
}

// now I can use getJson and get the result!
getJson('http://getjson.com', (res) => {
 console.log('got result!', res)
})
tommybananas
  • 5,518
  • 1
  • 24
  • 47
1

This is a pretty common hump to get over with async functions in node (and javascript in general).

What's happening is that your console.log(getJson(myUrl)) is called before the http request has returned anything. Basically, things like this won't work with async functions.

If you put your console.log() inside res.on('end) it will work. The way you need to deal with this if either put all your logic in the res.on('end) which kind of sucks, or pass a callback to your getJson() function which you call in res.on('end'), or wrap everything in a promise, which you can return from getJson().

To use a callback you would do something like this:

const getJson = function(url, callback) {
     // a bunch of code

    res.on('end', () => {
      try {
        const parsedData = JSON.parse(rawData);
        callback(null, parsedDate) // callbacks in node traditionaly pass an error as the first arg
     }
    //finish
}

The you call it with a function:

getJson(url, function(err, return_val) {
    if (err) // handle error
    console.log(return_val)
}

You can also look at other HTTP libraries like Axios that will return a promise without much work. With axios and similar libraries you can simply:

axios.get(url)
.then(response => {
    console.log(response);
  })
.catch(function (error) {
    console.log(error);
 });

Which is one of the reasons people use these libraries. More here: https://github.com/axios/axios

Mark
  • 74,559
  • 4
  • 81
  • 117
0

Because it runs asynchronously, it does not wait for the function call to end.

You can fix it with promise pattern.

Try something like this:

/**
 * Created by bagjeongtae on 2017. 10. 2..
 */
function parseData(url) {
    return new Promise((resolve, reject) => {
        https.get(url, (res) => {
            // get the status code
            const {statusCode} = res;
            const contentType = res.headers['content-type'];

            // check for the errors
            let error;
            if (statusCode !== 200) {
                reject('Request Failed.\n' + `Status Code: ${statusCode}`);
            } else if (!/^application\/json/.test(contentType)) {
                reject('Invalid content-type.\n' +
                    `Expected application/json but received ${contentType}`);
            }
            if (error) {
                console.error(error.message);
                reject(error.messag);
            }

            res.resume();

            //parse json
            res.setEncoding('utf8');
            let rawData = '';
            res.on('data', (chunk) => {
                rawData += chunk;
            });
            res.on('end', () => {
                try {
                    const parsedData = JSON.parse(rawData);
                    console.log(parsedData);
                    resolve(parseData);
                } catch (e) {
                    console.error(e.message);
                    reject(e.messag);
                }
            });
        });
    });
};

parseData('http://www.example.com').then( result =>{
    console.log(result);
}, err => {
    console.log(err);
})

Running getJson from console.log is asynchronous, so it does not wait for getJson to finish.

Asynchronous can be used like a synchronous.

멍개-mung
  • 390
  • 3
  • 8
-1

I think the output is correct.The getJson(myUrl) is return undefined since you not set a return in the getJson function,the javascript return undefined by default and the

{ user_id: <user-id>, name: 'Ajay Krishna Teja', email: <my-email> }

is the output by console.log(parsedData) in you code.

sinbar
  • 813
  • 5
  • 20
  • Although I didn't have a `return` statement in the example, as I have mentioned in things I have tried; including return didn't help. – PseudoAj Oct 02 '17 at 01:23
  • What I mean is, if no return in the synchronize thread, then default return undefined . – sinbar Oct 02 '17 at 01:32