flex-params

Multiple signatures for a function

Usage no npm install needed!

<script type="module">
  import flexParams from 'https://cdn.skypack.dev/flex-params';
</script>

README

  • v3.1.0
    • Support simpler syntax
    • Support pipe separated multiple types
  • v3.0.1 [Fix] Broken ES module loading since 3.0.0 has been fixed.
  • v3.0.0 The error handling API changed.

Build Status npm

flex-params is a tiny, but powerful utility for writing a function that has multiple signatures.

As you know, JavaScript doesn't support function overloading like this:

function foo(X)    { /* ... */ }
function foo(X, Y) { /* ... */ } // This makes the 1st foo() not callable

Yes, still you can do the same kind of thing with default parameters most of the time. But if you want more flexibility or scalability, flex-params allows you to define multiple combinations of parameters for a single function.

Getting Started

Install it with NPM:

npm i flex-params

And require() it:

const flexParams = require('flex-params');

or import it as an ES module:

import flexParams from 'flex-params';

Usage

In your function, pass an array of the arguments to flexParams() with any number of patterns of parameters (a.k.a. "signatures") you desired.

// Example Code
function foo(...args) {
  var result = {};

  flexParams(args, [
    { X:'string', Y:'int' },           // pattern #0
    { X:'string', Z:'bool', Y:'int' }, // pattern #1
    { Z:'bool', Y:'int', X:'string' }, // pattern #2
    ...                                // more patterns
  ], result);

  return result;
}

var r = foo('blah', 42);          // { X:'blah',   Y:42 }
var r = foo('blahh', true, 7);    // { X:'blahh',  Y:7,  Z:true }
var r = foo(false, 11, 'blaahh'); // { X:'blaahh', Y:11, Z:false }

In the code above, args is the array of arguments.

The 2nd parameter of flexParams() is an array of the patterns.
flexParams() tries to find the most suitable pattern in the array for args by comparing the type strings defined in each pattern with actual types of args.

Once it is found, each value of args is stored into result ( the 3rd parameter ) as its properties.

If you prefer simpler syntax, you can also write like this:

var result = flexParams(args, [
  ... // patterns
]);

Defining a pattern of parameters

Each pattern must be a plain object that has one of some specific formats.
The most basic format is like this:

// Pattern Definition
{
  param_1st: '<type>',
  param_2nd: '<type>',
  ...
  param_nth: '<type>'
}

'<type>' is a string representation of datatype (ex. 'bool', 'string', 'array', 'object', etc. ) for each param, like this:

{ foo:'string', bar:'boolean' }

This pattern means:

  • The 1st param is foo, and it must be a string
  • The 2nd param is bar, and it must be a boolean

You can also write multiple types separated by | (pipe) likes this:

{ foo:'string|number|boolean' }

This parameter matches with a string, a number or a boolean.

Default Values

Instead of just a type string, you can also use an array to define the default value:

{ foo:'string', bar:['boolean', false] }

Now the 2nd param bar turned to optional. The default value is false.

The pattern that contains optional parameters can be considered suitable even if a fewer number of arguments supplied. And the missing arguments will be filled with the default values of respective params.

How does it work?

// Example Code #1
function foo(...args) {
  let result = {};

  flexParams(args, [
    { flag:['boolean', false] },         // pattern #0
    { str:'string', num:['number', 1] }, // pattern #1
    { num:'number', flag:'boolean' }     // pattern #2
  ], result);

  return result;
}

You can see 3 patterns in the above example.
Let's test it by passing various combinations of arguments:

let test1 = foo();        // No argument
let test2 = foo('ABC');   // A string
let test3 = foo(8, true); // A number, A boolean

console.log('Test 1:', test1);
console.log('Test 2:', test2);
console.log('Test 3:', test3);

And these are the resulting objects:

Test 1: { flag: false }
Test 2: { str: 'ABC', num: 1 }
Test 3: { num: 8, flag: true }

Now you can see:

  • The test1 matched with pattern #0
  • The test2 matched with pattern #1
  • The test3 matched with pattern #2

More Example

// Example Code #2
const flexParams = require('flex-params');

class User {
  constructor(...args) {
    flexParams(args, [
      // patterns
      { firstName:'string', age:'int' },
      { firstName:'string', lastName:'string', age:'int' },
      { id:'int' },
      { login:'string', pass:Password }

    ], this); // Stores the args into 'this'
  }
}

class Password {
  constructor(key) {
    this.key = key;
  }
}

//// Test ////////////
let thomas = new User('Thomas', 20);
let john   = new User('John', 'Doe', 30);
let user1  = new User(1000);
let user2  = new User('d4rk10rd', new Password('asdf'));

console.log(thomas);
console.log(john);
console.log(user1);
console.log(user2);

Console outputs:

User { firstName: 'Thomas', age: 20 }
User { firstName: 'John', lastName: 'Doe', age: 30 }
User { id: 1000 }
User { login: 'd4rk10rd', pass: Password { key: 'asdf' } }

As you can see this example, you can pass this to the 3rd parameter of flexParams(). This way is useful for initializing the instance in the class constructor.

Advanced Usage

About the 3rd parameter of flexParams(), it accepts not only an object but also a function.

function foo(...args) {
  flexParams(args, [
    { flag:['boolean', false] },         // pattern #0
    { str:'string', num:['number', 1] }, // pattern #1
    { num:'number', flag:'boolean' }     // pattern #2

  ], (result, pattern) => { // Receiver Callback
    console.log('result:',  result);
    console.log('pattern:', pattern);
  });
  // Test ////////
  foo('XYZ', 512);
}

Console outputs:

result: { str: 'XYZ', num: 512 }
pattern: 1

Let's call this function a receiver callback. Receiver callback runs immediately after flexParams() finished processing args.

Receiver callback takes 2 parameters: result and pattern.
result is an object that contains all the args as its properties. pattern is the index number of the matched pattern, which is 1 ( means pattern #1 ) at this time.
This index is useful if you want to do some different things for each pattern with switch-case or if-else-if, like this:

function foo(...args) {
  return flexParams(args, [
    { flag:['boolean', false] },         // pattern #0
    { str:'string', num:['number', 1] }, // pattern #1
    { num:'number', flag:'boolean' }     // pattern #2

  ], (result, pattern) => { // Receiver Callback
    switch (pattern) { // Do stuff for each pattern
      case 0: return 'The first pattern matched.';
      case 1: return 'The second pattern matched.';
      case 2: return 'The last pattern matched.';
    }
  });
}
//// Test ////////
console.log( foo('XYZ', 512)   ); // 'The second pattern matched.'
console.log( foo(65535, false) ); // 'The last pattern matched.'
console.log( foo()             ); // 'The first pattern matched.'

Error Handling

If all the given patterns didn't match for the arguments, flexParams() returns false. But there is also another way to handle this situation. The 4th parameter: fallback.

fallback can be:

  • a) a callback,
  • b) an object, or
  • c) any other type of value ( except for undefined ).

a) If you passed a callback, it is called when all the given patterns mismatched.
Tha callback receives an object as its parameter with these properties:

  • args: The original array of arguments
  • patterns: The array of all the patterns
  • receiver: The receiver object or function passed to the 3rd parameter
  • error: An Exception object ( flexParams.InvalidArgument )

b) You can pass an plain object as the fallback with these optional properties:

  • log: A string to be sent to console.log()
  • warn: A string to be sent to console.warn()
  • error: A string to be sent to console.error()
  • throw: Any type of value to be thrown as an exception
    • If you set true , an Exception object is thrown ( flexParams.InvalidArgument )

c) Any other type of value as the fallback , is returned straightly by flexParams() if all the given patterns mismatched.

Appendix: Extra Types

flex-params supports some special types in addition to JavaScript's builtin datatypes.

Type Description
bool Alias of boolean
int, integer Matches for an integer
float, double Alias of number
array Matches for an array
iterable Matches for an array or an array-like object
primitive Matches for a primitive type value
any, mixed Matches for any type of value
A class constructor Matches for the class instances

© 2020 amekusa