I love writing tiny JavaScript modules that do just a single thing. I even started writing modules that export a single function. This makes it extremely simple to understand the module and start using it by just looking at a small documented example (I recommend using xplain to document javascript code). But what if my function needs to be configured? Let us take quote as an example. It exports single function that can safely add quotes to a given string, without double-quoting it
1 | var quote = require('quote'); |
What if a user wants to use single quote character instead of double quotes character? I could approach this problem in 3 different ways
1 - pass options object with each call
I always advocate using an options object instead of passing individual arguments: this makes the API a lot more robust to changing options. In this case, the options object would contains single property. The user could pass options object with each call.
1 | quote('foo', { quotes: '\'' }); // 'foo' |
In this case, the user most likely would always pass the same options object with each call. Because I advocate placing options least likely to change first in the function signature, I would actually make the API take the options object first. This makes it very convenient to do partial application
1 | var options = { quotes: '\'' }; |
2 - use global options method
Passing same info with each call seems weird, especially if the defaults are working just fine.
Thus I could create a separate config
function and attach it to the returned quote
function.
The config
would accept options object and would store these options as global properties.
1 | var quote = require('quotes'); |
The problem with this approach is subtle, but common to every shared data situation. We might set the options several times, and the last set of options always wins!
1 | quote.config({ quotes: '\'' }); |
3 - configure the local function closure
The typical argument to the function is string; I could detect if an object was passed in and assume it to be the options. In that case, I would return a new function that uses options from that options object.
First the top level interface:
1 | function quote(str) { |
Second, the actual worker that takes options object and string (just like method 1)
1 | function quoteOptions(options, str) { |
Using this approach you can configure and use multiple functions in parallel
1 | var wildcards = require('quote')({ quotes: '*' }); |
I kept the top exported function quote
(// 1
) simple, it could be combined with quoteOptions
to allow multiple configuration, but in this case it would only make the code more complex, and I do
not see the case for the following API use:
1 | var quote = require('quote')({ quotes: '*' }); |