Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@christroutner
Last active January 4, 2020 20:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save christroutner/3dd1e0155a76d0f38d084402aca24020 to your computer and use it in GitHub Desktop.
Save christroutner/3dd1e0155a76d0f38d084402aca24020 to your computer and use it in GitHub Desktop.
Standard JavaScript Function Layout
/*
This gist captures the basic pattern of a JavaScript function. It emcompasses patterns for best practices.
*/
// This global is used to reference the library instance when the 'this' context has been lost.
let _this
// With the switch to Classes for instantiating JS libraries, this is a common pattern:
// Note: An object starting with a capital letter denotes a Class: e.g. SomeExternalLibrary
const SomeExternalLibrary = require('./someExternalLibrary')
const someExternalLibrary = new SomeExternalLibrary()
// Objects with ALL_CAPITAL letters indicate a constant.
const SOME_CONSTANT = 4
// Encompasing libraries as a Class makes writing unit tests much easier.
class JSLib {
constructor() {
// Maintain reference to the instance of the library when context of 'this' has been lost,
// such as in a function call-back.
_this = this
// Use locally referenced libraries like this, rather than the global variable.
// This makes it easy to stub out external library functions using Sinon,
// when writing unit tests.
this.someExternalLibrary = someExernalLibrary
}
// Any time you can break large functions into smaller functions, it's a good idea.
// This example function will give an example of the basic layout pattern to follow.
async subFunction(inObj) {
// The entire inner function should be wrapped in a try/catch statement.
try {
// Creating a return value and setting it to a negative result means that the
// output of your function is predictable. If nothing else works, you'll at
// least know that your function will be returning a known value.
let returnVal = false
// Passing a single input object is handy because it's easy to expand for additional
// functionality later, without impacting current functionality.
const {var1, var2, var3} = inObj
// The first section of any function should be the 'input validation' section. If
// the inputs are out of range, then you can throw an Error. These should also
// be the first unit tests written.
if(typeof(var1) !== 'string') throw new Error(`var1 must be a string`)
if(var2 < 0) throw new Error(`var2 must be a positive integer`)
// After input validation, do the meat of the work, e.g. the business logic.
if(var1 === 'a') {
returnVal = 'This is the output for a'
// A more complicated code path.
} else {
const preppedObj = var2 + var3 + SOME_CONSTANT
// Using _this means you don't have to remember what the context of 'this' is.
// Also alows easy stubbing of 'usefulFunc' with Sinon when writing unit tests.
const result = await _this.someExternalLibrary.usefulFunc(preppedObj)
// Exceptions or unexpected behavior should be handled by throwing an error with
// an informative and unique message.
if(!result) throw new Error(`usefulFunc() returned unexpected result`)
returnVal = result
}
// Return the variable you set at the top. If nothing in between has changed its state,
// the default negative value is assured.
return returnVal
} catch(err) {
// Optional. This console.error statement will probably be commented out for production,
// or added to a logging system like Winston (instead of the console).
// But when developing and debugging, it's great to follow a chain of console logs like
// this to quickly see where the error originated.
console.error(`Error in someExternalLibrary.js/subFunction()`)
// The error should be thrown at the end, unless there is a specific reason not to.
// This lets errors 'bubble up' to parent functions when they are not handled
// in the child functions.
throw err
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment