Preventing function signature mismatch in ES5 and ES6

[2014-09-02] esnext, dev, javascript, jslang
(Ad, please don’t block)

In some cases, using a function (or method) with a callback can lead to surprising results – if the signature of the latter does not match the expectations of the former. This blog post explains this phenomenon and suggests fixes.

Function signature mismatch  

Let’s look at an example [1]:

> ['1','2','3'].map(parseInt)
[1,NaN,NaN]

Here, map() expects the following signature:

callback(element, index, array)

But parseInt() has the signature:

parseInt(string, radix?)

It’s not a problem that parseInt’s arity is less than the 3 expected by map; JavaScript does not complain if you ignore arguments. However, index and the optional radix don’t match semantically.

Whenever you are using a library function as a callback, you are taking a risk: its signature may not match semantically, it may even change later on.

Preventing mismatch  

Prevention via arrow functions  

In ECMAScript 6, arrow functions [2] give you the means to be explicit about the signature of a callback, without too much verbosity:

> ['1', '2', '3'].map(x => parseInt(x))
[1, 2, 3]

I like using arrow functions for this purpose: it’s compact and you immediately see how the code works.

Prevention via a helper function  

Another option for preventing signature mismatch is to use a higher-order helper function, e.g.:

> ['1', '2', '3'].map(passArgs(parseInt, 0))
[1, 2, 3]

passArgs has the following signature:

passArgs(toFunction, argIndex0, argIndex1, ...)

The indices indicate which of the input parameters toFunction receives and in which order. The following is an implementation for ECMAScript 5.

function passArgs(toFunction /* argIndex0, argIndex1, ... */) {
    var indexArgs = arguments;
    return function () {
        var applyArgs = new Array(indexArgs.length-1);
        for(var i=0; i < applyArgs.length; i++) {
            var index = indexArgs[i+1];
            applyArgs[i] = arguments[index];
        }
        return toFunction.apply(this, applyArgs);
    };
}

The following is an implementation for ECMAScript 6. Note how much simpler it is, due to rest parameters and arrow functions.

function passArgs(toFunction, ...argIndices) {
    return function (...inputArgs) {
        var passedArgs = argIndices
            .map(argIndex => inputArgs[argIndex]);
        return toFunction.apply(this, passedArgs);
    };
}

References  


  1. Pitfall: Unexpected Optional Parameters” in Speaking JavaScript ↩︎

  2. ECMAScript 6: arrow functions and method definitions ↩︎