Home Blog CV Projects Patterns Notes Book Colophon Search

Node.js Snippits

20 Jan, 2017

Here are some useful snippits of code for working with Node.js

Fetch JSON from a URL

This is a function that can be used to get JSON from a URL. It supports a timeout.

const url = require('url')

function fetchJson (spec) {
  const {get, log, url: inputUrl, method, headers, timeout, allowedStatusCodes = [200]} = spec
  let runningTimeout;
  return new Promise((resolve, reject) => {
    if (method && (method !== 'GET')) {
      reject({msg: 'fetch.fetchJson: Only GET is supported'})
    }
    const options = Object.assign(url.parse(inputUrl, true), {headers})
    log(`Making a GET request to ${inputUrl}`)
    const request = get(options, (response) => {
      let body = ''
      if (allowedStatusCodes && (allowedStatusCodes.indexOf(response.statusCode) === -1)) {
        request.abort()
        reject({msg: `fetch.fetchJson: unexpected response status code ${response.statusCode}`, detail: allowedStatusCodes})
      }
      response.on('data', (d) => {
        body += d
      })
      response.on('end', () => {
        if (runningTimeout) {
          clearTimeout(runningTimeout)
        }
        try {
          resolve(JSON.parse(body))
        } catch (error) {
          reject({msg: 'fetch.fetchJson: parsing error', detail: error, body: body})
        }
      })
      response.on('error', (error) => {
        if (runningTimeout) {
          clearTimeout(runningTimeout)
        }
        reject({msg: 'fetch.fetchJson response error', detail: error, body: body})
      })
    })
    request.on('error', (error) => {
      if (runningTimeout) {
        clearTimeout(runningTimeout)
      }
      reject({msg: 'fetch.fetchJson request error', error: error})
    })
    if (timeout) {
      runningTimeout = setTimeout(() => {
        request.abort()
        reject({msg: `fetch.fetchJson timed out after ${timeout}ms`, error: 'Timeout'})
      }, timeout)
    }
  })
}

Used like this:

function log (...args){
  console.log(...args)
}

const https = require('https')

fetchJson({
  url: 'https://jimmyg.org/some/json.json',
  timeout: 1000,
  log,
  get: https.get,
  method: 'GET',
  allowedStatusCodes: [200, 404],
  headers: {},
}).then((result) => {
  log('SUCCESS', JSON.stringify(result, null, 4))
}).catch((error) => {
  log('FAIL', JSON.stringify(error, null, 4))
})

This gives the following error because the URL doesn't exist.

Making a GET request to https://jimmyg.org/some/json.json
FAIL {
    "msg": "fetch.fetchJson: parsing error",
    "detail": {},
    "body": "<html>\n<head><title>James Gardner</title></head>\n<body>\n\n<h1>Not Found</h1>\n\n<p>Perhaps that page is still being migrated. If you think the page should exist, please drop me a line at james at pythonweb.org.</p>\n</body>\n</html>"
}

For an insecure version, use http.get.

This line is really useful:

JSON.stringify(result, null, 4);

It produces JSON indented 4 spaces.

You can make the usage less verbose with the help of withDefaults().

Once

You sometimes run into a situation where it is useful to break code into smaller parts but have each of those dependencies share access to an expensive resource.

const assert = require('assert')

function once (target, spec) {
  let called = false
  let lastSpec
  if (called !== false) {
    assert.deepStrictEqual(spec, lastSpec, 'Called with different params')
  }
  return function () {
    if (called === false) {
      called = target(spec)
      lastSpec = spec
    }
    return called
  }
}

Tooling

I also like to install standard and nyc globally so that I can use them on scripts like the above:

sudo npm install -g standard nyc

Then:

standard --fix s.js
nyc node s.js

The coverage output might look like this:

[ 1, 2 ]
[ { ok: false, error: 1 }, { ok: true, result: 2 } ]
----------|----------|----------|----------|----------|----------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
----------|----------|----------|----------|----------|----------------|
All files |      100 |      100 |      100 |      100 |                |
 s.js     |      100 |      100 |      100 |      100 |                |
----------|----------|----------|----------|----------|----------------|

Read a file

const fs = require('fs')

function readFile (spec) {
  const {path, encoding = 'utf8'} = spec
  return new Promise((resolve, reject) => {
    try {
      fs.readFile(path, encoding, (error, data) => {
        if (error) {
          reject({error})
        } else {
          resolve(data)
        }
      })
    } catch (error) {
      reject({error})
    }
  })
}

module.exports = readFile

Asset module

Node also comes with an assert module so instead of running tests using a runner, you can also just write a test program that imports your module, executes code and makes assertions. I like this style of testing without frameworks.

You can always print to the terminal in different colours like this:

console.log('\x1b[32m%s\x1b[0m', 'green');
console.log('\x1b[31m%s\x1b[0m', 'red');
console.log('\x1b[33m%s\x1b[0m', 'yellow');
console.log('\x1b[37m%s\x1b[0m', 'light grey');
console.log('\x1b[90m%s\x1b[0m', 'dark grey');

Or print Unicode characters like this:

console.log('✗')
console.log('✓')

Or combine both!

console.log('\x1b[32m%s\x1b[0m', '✓');
console.log('\x1b[31m%s\x1b[0m', '✗');

Copyright James Gardner 1996-2020 All Rights Reserved. Admin.