josdejong / typed-function

Runtime type-checking for JavaScript functions
MIT License
71 stars 19 forks source link

typed-function

Version Downloads Build Status

Move type checking logic and type conversions outside of your function in a flexible, organized way. Automatically throw informative errors in case of wrong input arguments.

Features

typed-function has the following features:

Supported environments: node.js, Chrome, Firefox, Safari, Opera, IE11+.

Why?

In JavaScript, functions can be called with any number and any type of arguments. When writing a function, the easiest way is to just assume that the function will be called with the correct input. This leaves the function's behavior on invalid input undefined. The function may throw some error, or worse, it may silently fail or return wrong results. Typical errors are TypeError: undefined is not a function or TypeError: Cannot call method 'request' of undefined. These error messages are not very helpful. It can be hard to debug them, as they can be the result of a series of nested function calls manipulating and propagating invalid or incomplete data.

Often, JavaScript developers add some basic type checking where it is important, using checks like typeof fn === 'function', date instanceof Date, and Array.isArray(arr). For functions supporting multiple signatures, the type checking logic can grow quite a bit, and distract from the actual logic of the function.

For functions dealing with a considerable amount of type checking and conversion logic, or functions facing a public API, it can be very useful to use the typed-function module to handle the type-checking logic. This way:

It's important however not to overuse type checking:

Load

Install via npm:

npm install typed-function

Usage

Here are some usage examples. More examples are available in the /examples folder.

import typed from 'typed-function'

// create a typed function
var fn1 = typed({
  'number, string': function (a, b) {
    return 'a is a number, b is a string';
  }
});

// create a typed function with multiple types per argument (type union)
var fn2 = typed({
  'string, number | boolean': function (a, b) {
    return 'a is a string, b is a number or a boolean';
  }
});

// create a typed function with any type argument
var fn3 = typed({
  'string, any': function (a, b) {
    return 'a is a string, b can be anything';
  }
});

// create a typed function with multiple signatures
var fn4 = typed({
  'number': function (a) {
    return 'a is a number';
  },
  'number, boolean': function (a, b) {
    return 'a is a number, b is a boolean';
  },
  'number, number': function (a, b) {
    return 'a is a number, b is a number';
  }
});

// create a typed function from a plain function with signature
function fnPlain (a, b) {
  return 'a is a number, b is a string';
}

fnPlain.signature = 'number, string';
var fn5 = typed(fnPlain);

// use the functions
console.log(fn1(2, 'foo'));      // outputs 'a is a number, b is a string'
console.log(fn4(2));             // outputs 'a is a number'

// calling the function with a non-supported type signature will throw an error
try {
  fn2('hello', 'world');
} catch (err) {
  console.log(err.toString());
  // outputs:  TypeError: Unexpected type of argument.
  //           Expected: number or boolean, actual: string, index: 1.
}

Types

typed-function has the following built-in types:

The following type expressions are supported:

Dispatch

When a typed function is called, an implementation with a matching signature is called, where conversions may be applied to actual arguments in order to find a match.

Among all matching signatures, the one to execute is chosen by the following preferences, in order of priority:

When this process gets to the point of comparing individual parameters, the preference between parameters is determined by the following, in priority order:

If none of these aspects produces a preference, then in those contexts in which Array.sort is stable, the order implementations were listed when the typed-function was created breaks the tie. Otherwise the dispatch may select any of the "tied" implementations.

API

Construction

typed([name: string], ...Object.<string, function>|function)

A typed function can be constructed from an optional name and any number of (additional) arguments that supply the implementations for various signatures. Each of these further arguments must be one of the following:

The name, if specified, must be the first argument. If not specified, the new typed-function's name is inherited from the arguments it is composed from, as long as any that have names agree with one another.

If the same signature is specified by the collection of arguments more than once with different implementations, an error will be thrown.

Properties and methods of a typed function fn

Methods of the typed package

Properties

Recursion

The this keyword can be used to self-reference the typed-function:

var sqrt = typed({
  'number': function (value) {
    return Math.sqrt(value);
  },
  'string': function (value) {
    // on the following line we self reference the typed-function using "this"
    return this(parseInt(value, 10));
  }
});

// use the typed function
console.log(sqrt('9')); // output: 3

Roadmap

Version 4

Version 5

Test

To test the library, run:

npm test

Code style and linting

The library is using the standardjs coding style.

To test the code style, run:

npm run lint

To automatically fix most of the styling issues, run:

npm run format

Publish

  1. Describe the changes in HISTORY.md
  2. Increase the version number in package.json
  3. Test and build:
    npm install
    npm run build-and-test
  4. Verify whether the generated output works correctly by opening ./test/browserEsmBuild.html in your browser.
  5. Commit the changes
  6. Merge develop into master, and push master
  7. Create a git tag, and push this
  8. publish the library:
    npm publish