Portrait Dr. Axel Rauschmayer
Dr. Axel Rauschmayer
Homepage | Twitter
Cover of book “Exploring ES6”
Book, exercises, quizzes
(free to read online)
Logo of newsletter “ES.next news”
Newsletter (free)
Cover of book “JavaScript for impatient programmers”
Book (free online)

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 ↩︎