Skip to content Skip to sidebar Skip to footer

How To Return The Response From A Nodejs' HTTPS GET Request?

I am still learning node.js, so please be kind. I am struggling to get my head around some of the basics without having a book open. I have written a function to go out and get som

Solution 1:

Ah, the joys of learning Javascript's asynchronous programming model!

This line

const connection = authenticate( 'DATATOBEPASSED' , 'https://URLHERE');

returns to its caller before either event handler -- res.on("data", ...) and res.on("data", ...) get called with the results of your get operation.

You need to use a callback from your authenticate() function to deliver the results to its caller.

function authenticate( uuid , cdcloc, callback ) { 
    let url = cdcloc + "/api.php?uuid=" + uuid + '&auth';
    https.get(url,(res) => {
        let body = "";
        res.on("data", (chunk) => {
            body += chunk;
        });
        res.on("end", () => {
            try {
                let cdcResponse = JSON.parse(body);
                // do something with JSON
                callback(cdcResponse[0]);
            } catch (error) {
                console.error(error.message);
            };
        });
    }).on("error", (error) => {
        console.error(error.message);
    });
}
authenticate( 'DATATOBEPASSED' , 'https://URLHERE',
    function (connection) {
      console.log(connection.SerialNumber);
    }
 );

There are language features known as Promises and async / await to help escape the confusing mess of nested callbacks we get into when we write significant code.


Solution 2:

node-style callbacks

The answer from O.Jones is correct but it goes against Node's convention of error-first callbacks. I think it is also a mistake to reach for https.get every single time you need to make a request. It is a low-level function and because it asks you to connect so many bits and pieces, it is likely you will make easily-avoidable mistakes.

We can write a generic getString function that wraps https.get -

const https = require('https')

function getString(url, options, callback)
{ https
    .get(url, options, res => {
      let s = "";
      res.on("data", d => s += d)
      res.on("end", _ => callback(null, s) // error-first callback
    })
    .on("error", e => callback(e))         // error-first callback
}

Now that we have a generic function to fetch a string, we don't need to write res.on("data, ...) and res.on("end", ...) in every function that makes a request. But don't stop here. You will often want to JSON.parse the result -

function getJSON(url, options, callback)
{ getString(url, options, function(err, data)
  { if (err) callback(err)                         // getString error
    else try { callback(null, JSON.parse(data) }   // JSON.parse success
    catch (e) { callback(e) }                      // JSON.parse error
  }
}

Now we can write authenticate without having touch the bare https.get or worrying about parsing JSON each time -

function authenticate(uuid, cdcloc, callback)             // callback
{ const url = cdcloc + "/api.php?uuid=" + uuid + '&auth'
  getJSON(url, {}, function(err, json)
  { if (err)
      callback(err)                                       // getJSON error
    else if (json.length == 0)
      callback(Error("empty response"))                   // empty response error
    else
      callback(null, json[0])                             // success
  }
}

promises

But all of this is pretty painful still, isn't it? Enter Promises. Node-style callbacks were designed at a time when we didn't have access to async control flow primitives. We've come a long way since then. To see how promises work, we will re-implement the functions above, but this time without the need to pass callback and error-check everywhere -

const https = require('https')

function getString(url, options)           // no callback
{ return new Promise((resolve, reject) =>  // return Promise
  { https
      .get(url, options, res => {
        let s = "";
        res.on("data", d => s += d)
        res.on("end", _ => resolve(s))     // success, resolve
      })
      .on("error", e => reject(e))         // failure, reject
  }
}

We immediately see the benefits of our new implementation when we rewrite getJSON -

function getJSON(url, options = {})   // no callback
{ return getString(url, options)      // return promise
    .then(s => JSON.parse(s))         // errors auto bubble up
}

And more benefits when we write authenticate -

function authenticate(uuid, cdcloc)                   // no callback
{ const url = `${cdcloc}/api.php?uuid=${uuid}&auth`
  return getJSON(url)                                 // return promise
    .then(data => {
      if (data.length == 0)
        throw Error("empty response")                 // local error
      else
        return data[0]                                // success
    })                                                // no error-check
}

async/await

Even Promises have been around for a long time and we've learned a lot since their native inclusion in ECMAScript. Remember to return promises and having to sequence all of the data through .then calls is tedious, the same way writing those initial res.on("data", ...) and res.on("end", ...) handlers felt. async and await keywords allows us to work with asynchronous control flow without having to sacrifice synchronous programming style -

async function getJSON(url, options = {})   // async
{ const s = await getString(url, options)   // await
  return JSON.parse(s)                      // auto wrapped in Promise
}

Writing authenticate is easy and feels natural -

async function authenticate(uuid, cdcloc)                 // async 
{ const url = `${cdcloc}/api.php?uuid=${uuid}&auth`
  const data = await getJSON(url)                         // await
  if (data.length == 0)
    throw Error("empty response")                         // throw if empty
  else
    return data[0]                                        // return first
}

Using it is easy and feels natural too -

async function connect()
{ const connection = await authenticate( 'DATATOBEPASSED' , 'https://URLHERE')
  console.log(connection.SerialNumber)
  // ...
  return "done"                            // or whatever
}

connect().then(console.log, console.error) // errors bubble all the way up

URL

I should also mention that building URLs with string concatenation is tedious and prone to a host of errors. You should develop a sense for this pain and know that it means there's room for relief. Take a look at the URL module that can safely build/manipulate URLs in pretty much every way imaginable.


Post a Comment for "How To Return The Response From A Nodejs' HTTPS GET Request?"