pengz
pengz

Reputation: 2471

NodeJS https module create reusable function to send request and handle response dynamically

This question is similar to this older question but I was not able to get the accepted answer to work correctly.

I am using the built-in NodeJS 'https' module to make requests to an external API. NodeJS version 12.

node: 12.16
express: 4.16.1

I was able to get it working with the example code from the documentation.

router.get('/', (req, res, next) => {
  const requestOptions = httpCtrl.getReqOptions();

  // Working example
  // How to separate this logic into reusable function?
  const request = https.request(requestOptions, (response) => {
    let result = {
      status: null,
      data: {}
    };

    let rawData = '';
    response.on('data', (chunk) => {
      rawData += chunk;
    });

    response.on('end', () => {
      console.log('No more data in response.');
      try {
        parsedData = JSON.parse(rawData);
        result.status = parsedData.status || 200;
        result.data = parsedData;
        return res.status(result.status).json(result);
      } catch (e) {
        result.status = 500;
        result.data.message = `ERROR: Unable to parse API response`;
        result.data.exception = e;
        return res.status(result.status).send(result);
      }
    });

  });

  request.on('error', (e) => {
    result.status = 500;
    result.data.message = `ERROR: API response`;
    result.data.exception = e;
    return res.status(result.status).send(result);
  });
  request.end();
});

However, I want to break out this logic into a reusable function, and just pass it the request options dynamically.

I tried just creating a synchronous function wrapper and returning the results, but obviously that didn't work because the sync function does not wait for the completion of the async request.

httpCtrl = {};

httpCtrl.createRequest = (requestOptions) => {

  // Does not work due to being synchronous, also tried with async await to no avail
  const request = https.request(requestOptions, (response) => {
    let result = {
      status: null,
      data: {}
    };

    let rawData = '';
    response.on('data', (chunk) => {
      rawData += chunk;
    });

    response.on('end', () => {
      console.log('No more data in response.');
      try {
        parsedData = JSON.parse(rawData);
        result.status = parsedData.status || 200;
        result.data = parsedData;
        return result;
      } catch (e) {
        result.status = 500;
        result.data.message = `ERROR: Unable to parse NRS Admin API response`;
        result.data.exception = e;
        return result;
      }
    });

  });

  request.on('error', (e) => {
    result.status = 500;
    result.data.message = `ERROR: API response`;
    result.data.exception = e;
    return result;
  });
  request.end();
});

}

router.get('/', (req, res, next) => {

  const requestOptions = httpCtrl.setRequestOptions();
  const result = httpCtrl.createRequest(requestOptions);
  return res.status(result.status).send(result);

});

How can I update this code to be more re-usable?

Upvotes: 1

Views: 514

Answers (1)

MAS
MAS

Reputation: 736

Transform createRequest function to a promise, promises work like callbacks except they are much better to read.

// *** createReuqest function is a Promise ***
httpCtrl.createRequest = (requestOptions) => {
  return new Promise((resolve, reject) => {
    const result = {};
   // *** http.request function is a Callback ***
    const request = http.request(requestOptions, response => {
      let rawData = '';
      response.on('data', chunk => rawData += chunk);
      // resolve the promise when response ends
      response.on('end', () => {
        result.status = response.status || 200;
        result.data = rawData;
        resolve(result);
      });
    });
    // or reject on error
    request.on('error', e => {
      result.status = 500;
      result.data = {
        message: 'ERROR: API response',
        exception: e
      };
      reject(result);
    });
    request.end();
  });
};

Now we simply call the function and we chain it with then and catch, however, I choose to use async/await to include all asynchronous JavaScript in this example :) async/await is based on promises but with even cleaner markup.

// *** Finally async/await ***
router.get('/', async (req, res) => {
  // initial options for testing
  const requestOptions = {
    hostname: 'www.google.com',
    port: 443,
    method: 'GET'
  };
  // await must be in try/catch to properly handle promise's resolve/reject
  try {
    const response = await httpCtrl.createRequest(requestOptions);
    res.status(response.status).send(response.data);
  } catch (error) {
    res.status(error.status).send(error.data);
  }
});

Hope I've helped.

Upvotes: 2

Related Questions