Instantly share code, notes, and snippets.

Embed
What would you like to do?
An Easier Way to Enforce Required Parameters in ES6

An Easier Way to Enforce Required Parameters in ES6

Expands on Handling required parameters in ECMAScript 6 by Axel Rauschmayer.

The idea (which is credited to Allen Wirfs-Brock) is, in essence, to use default parameter values to call a function which throws an Error if the parameter is missing:

const throwIfMissing () => { throw new Error('Missing parameter') }

const foo = (mustBeProvided = throwIfMissing()) => {}

foo() // ==> Error: Missing parameter

This idea is great. However, the Error message does not tell you which parameter is missing, which could be quite helpful if the function had multiple parameters or an option object with multiple options, for example. To give more accurate Error feedback, we could refactor the code as follows:

const throwIfMissing = p => { throw new Error(`Missing parameter: ${p}`) }

const foo = (mustBeProvided = throwIfMissing('mustBeProvided')) => {}

foo() // ==> Error: Missing parameter: mustBeProvided

Now, the code for such a seemingly simple task, enforcing that a parameter is required, has become quite long, however: minus the parameter name, throwIfMissing('') is 18 characters long.

Since this is such a fundamental and often used task, it makes sense to make it as short and easy-to-type as possible.

If we use template literal syntax foo´string´ for calling the function instead of the normal round brackets syntax foo('string'), we can already reduce the number of characters by 2.

const throwIfMissing = p => { throw new Error(`Missing parameter: ${p}`) }

const foo = (mustBeProvided = throwIfMissing`mustBeProvided`) => {}

foo() // ==> Error: Missing parameter: mustBeProvided

Nevertheless, it is still 16 characters long, excluding the parameter name, of course. Now, the only option left to reduce the number of characters needed for this simple task is to rename the throwIfMissing function.

There are quite a few shorter alternatives which I like, for example:

  • throwIfNo (e.g. throwIfNo´password´)
  • enforce (e.g. enforce´password´)
  • ensure (e.g. ensure´password´)

My favorite, however, is just x. Why an x?

  • Well, the x represents a check mark: [x].
  • It's also super short. Now, we're from 18 characters down to 3 (plus the parameter name).

Before:

const throwIfMissing () => { throw new Error('Missing parameter') }

const foo = (mustBeProvided = throwIfMissing()) => {}

foo() // ==> Error: Missing parameter

After:

const x = p => { throw new Error(`Missing parameter: ${p}`) }

const foo = (mustBeProvided = x`mustBeProvided`) => {}

foo() // ==> Error: Missing parameter: mustBeProvided

Simple "real world" example using throw-if-missing:

const x = require('throw-if-missing')

const login = ({ username = x`username`, password = x`password` } = {}) => {}

login({ username: 'C-3PO' }) // ==> Error: Missing password
@adyngom

This comment has been minimized.

Copy link

adyngom commented Sep 23, 2017

Very good for the more explicit error message, but I don't think we gain much from the conciseness of x versus throwIfmissing. We are sacrificing readability here and intuitiveness for pure character gains and nothing else IMO

@MarkTiedemann

This comment has been minimized.

Copy link
Owner Author

MarkTiedemann commented Sep 25, 2017

@adyngom I understand your point, and I agree with you 98.7654321%.

I do think readability can be improved by shorter names in very specific cases. ava, for example, uses t for assertions (e.g. assert that the answer is 42: t.is(theAnswer, 42). Now, on the one hand, if you know ava and you know that t, by convention, means assert, it is very easy, quick and convenient for you to read tests with many assertions. On the other hand, if you don't know ava and you don't know the conventions, then you're gonna wonder: What the hell is this mystic t supposed to be? You then hopefully check the docs and, within 1min, you realize that t === assert. As you continue using ava, it becomes second nature for you to assert using t and you forget you were ever surprised in the first place.

There's a different argument for writability. Typing x is as simple as it gets. Typing throwIfMissing, there are plenty of opportunities to mistype.

Another supporting argument, I guess, is that required params should be a language feature so we don't have to come up with workarounds like these. Perhaps, in future, we can type something like login(~username, ~password) and be done with it. Is the ~ char for throwIfMissing more expressive than the x? I don't know. But if that's part of the language, you're gonna accept it in no time. I think we humans are quite flexible in this regard. :)

@marcin-chwedczuk

This comment has been minimized.

Copy link

marcin-chwedczuk commented Jan 1, 2018

I think this will bloat method signatures. I think it would be a better idea to add checking inside method like this:

constructor(x) {
  this.x = checkRequired(x, "x");
}

and also mark parameters as required in e.g. JSDoc.

@MarkTiedemann

This comment has been minimized.

Copy link
Owner Author

MarkTiedemann commented May 15, 2018

@marcin-chwedczuk I don't see how one or the other is more or less bloated; the checks are just positioned in different places.

// In Body
function foo(param) {
  console.log(x(param, `param`))
}

// In signature
function foo(param = x`param`) {
  console.log(param)
}

Actually, I prefer seeing whether a parameter is required in the signature so I don't have to check the body of the function (which is especially useful when working with larger functions).

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