34544

Node js lost in asynchronous behaviour: undefined

Question:

<h2>Objective</h2> <blockquote>

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

</blockquote>

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

<h2>Code</h2> 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)); <h2>Output</h2> undefined { user_id: <user-id>, name: 'Ajay Krishna Teja', email: <my-email> } <h2>Issue</h2>

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

<h2>Things I tried</h2> <ol><li>Returning parsedData on res.on(end) block</li> <li>Defining a var and copying parsedData</li> <li>Copying to a global variable (although I knew it's very bad practice)</li> </ol><h2>Places I looked up</h2> <ol><li><a href="https://stackoverflow.com/questions/19850234/node-js-variable-declaration-and-scope" rel="nofollow">Node.js variable declaration and scope</a></li> <li><a href="https://stackoverflow.com/questions/19539391/how-to-get-data-out-of-a-node-js-http-get-request" rel="nofollow">How to get data out of a Node.js http get request</a></li> <li><a href="https://stackoverflow.com/questions/35308331/javascript-function-returning-undefined-value-in-node-js" rel="nofollow">Javascript function returning undefined value in node js</a></li> </ol><h2>Updated: Working code</h2> 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); });

Answer1:

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 <strong>parameter</strong> 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 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" rel="nofollow">Promises</a> 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) })

Answer2:

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: <a href="https://github.com/axios/axios" rel="nofollow">https://github.com/axios/axios</a>

Answer3:

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.

Answer4:

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.

Recommend

  • abstract class and anonymous class [duplicate]
  • How to remove a json string from list in python
  • How to enable highcharts x and y axis scrollbars?
  • WPF MVVM Default Focus on Textbox and selectAll
  • ng -repeat repeating data with duplicate entries
  • Get all the td tags of table which in div tag using JQUERY
  • Pass href dynamically in an XML file
  • How to get rid of MemoryError while dealing with a large dictionary?
  • How to send data along with file in http POST (angularjs + expressjs)?
  • Cannot read property 'addEvent' of undefined
  • Write “NOT IN” in Doctrine Query Language
  • Will these ActiveXObject and XMLHttpRequest checks apply for any other browser than IE6?
  • Undefined reference to my own library
  • javascript array numerical key resulting in excess “undefined”
  • Can one add a complex type item to ListModel?
  • JavaScript Interface to a Browser not working in 4.2.2
  • Android getAssets in Common class gives “The method getAsssets() is undefined for the type Context”
  • Laravel lmutator $this->attributes return 'Undefined index: id'
  • pygame.init() shows as undefined variable after installing Pygame
  • Thread synchronization with syncwarp
  • How to Add Polymorphic Comments to Feed?
  • get iframe content as string
  • Angular2 - Template reference inside NgSwitch
  • User messaging system
  • Getting error 'Cannot read property 'document' of undefined' while importing exp
  • Laravel: Getting Session ID oddly truncates when using foreach
  • How do I access an unhandled exception in an MVC Error view?
  • WPF - CanExecute dosn't fire when raising Commands from a UserControl
  • Email verification using google app script and google forms
  • Email format validation in mvc3 view
  • How to make a tree having multiple type of nodes and each node can have multiple child nodes in java
  • Accessing IRQ description array within a module and displaying action names
  • Javascript Callbacks with Object constructor
  • Where to put my custom functions in Wordpress?
  • sending/ receiving email in Java
  • How to delete a row from a dynamic generate table using jquery?
  • json Serialization in asp
  • Rails 2: use form_for to build a form covering multiple objects of the same class
  • C# - Getting references of reference
  • How to stop GridView from loading again when I press back button?