Skip to content

Instantly share code, notes, and snippets.

@ktheory
Last active May 11, 2024 10:59
Show Gist options
  • Save ktheory/df3440b01d4b9d3197180d5254d7fb65 to your computer and use it in GitHub Desktop.
Save ktheory/df3440b01d4b9d3197180d5254d7fb65 to your computer and use it in GitHub Desktop.
Easily make HTTPS requests that return promises w/ nodejs

Javascript request yak shave

I wanted to easily make HTTP requests (both GET & POST) with a simple interface that returns promises.

The popular request & request-promises package are good, but I wanted to figure out how to do it w/out using external dependencies.

The key features are:

  • the ability to set a timeout
  • non-200 responses are considered errors that reject the promise.
  • any errors at the TCP socker/DNS level reject the promise.

Hat tip to webivore and the Node HTTP docs, whose learning curve I climbed.

It's great for AWS Lambda.

Examples

const url = require('url');
const req = require('httpPromise');
// Simple get request
req("https://httpbin.org/get")
  .then((res) => { console.log(res)})
  .catch((err) => { console.log("oh no: " + err)});

// Get req w/ timeout and extra headers:
req(Object.assign({}, url.parse("https://httpbin.org/get"), {timeout: 2000, headers: {'X-MyHeader': 'MyValue'}}))

// POST data:
req(Object.assign({}, url.parse("https://httpbin.org/post"), { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}}), "foo=bar")
  .then((res) => { console.log(res)})
  .catch((err) => { console.log("oh no: " + err)});
import https from 'https'
export default (urlOptions, data = '') => new Promise((resolve, reject) => {
// Inspired from https://gist.github.com/ktheory/df3440b01d4b9d3197180d5254d7fb65
const req = https.request(urlOptions, res => {
// I believe chunks can simply be joined into a string
const chunks = []
res.on('data', chunk => chunks.push(chunk))
res.on('error', reject)
res.on('end', () => {
const { statusCode, headers } = res
const validResponse = statusCode >= 200 && statusCode <= 299
const body = chunks.join('')
if (validResponse) resolve({ statusCode, headers, body })
else reject(new Error(`Request failed. status: ${statusCode}, body: ${body}`))
})
})
req.on('error', reject)
req.write(data, 'binary')
req.end()
})
@hertzg
Copy link

hertzg commented Aug 1, 2022

for people who just want to deal with buffers and nothing more (also with listener cleanup)

import https from "https";

export const request = (urlOptions, body) =>
  new Promise((resolve, reject) => {
    const handleRequestResponse = (res) => {
      removeRequestListeners();

      const chunks = [];
      const handleResponseData = (chunk) => {
        chunks.push(chunk);
      };

      const handleResponseError = (err) => {
        removeResponseListeners();
        reject(err);
      };

      const handleResponseEnd = () => {
        removeResponseListeners();
        resolve({ req, res, body: Buffer.concat(chunks) });
      };

      const removeResponseListeners = () => {
        res.removeListener("data", handleResponseData);
        res.removeListener("error", handleResponseError);
        res.removeListener("end", handleResponseEnd);
      };

      res.on("data", handleResponseData);
      res.once("error", handleResponseError);
      res.once("end", handleResponseEnd);
    };

    const handleRequestError = (err) => {
      removeRequestListeners();
      reject(err);
    };

    const removeRequestListeners = () => {
      req.removeListener("response", handleRequestResponse);
      req.removeListener("error", handleRequestError);
    };

    const req = https.request(urlOptions);
    req.once("response", handleRequestResponse);
    req.once("error", handleRequestError);

    if (body) {
      req.end(body);
    } else {
      req.end();
    }
  });

@santanaG
Copy link

@santanaG Thank you! I updated the gist w/ the code in your comment.

That was unexpected but I am honored! Thanks!

@Et7f3
Copy link

Et7f3 commented Jan 20, 2024

@hertzg Why do you removeRequestListeners if you only add once ?

Using once might not be needed because:

The optional callback parameter will be added as a one-time listener for the 'response' event.

From documentation 'abort' event can be emitted

'abort' ('aborted' on the res object)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment