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.
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.
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.
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);
};
}
“Pitfall: Unexpected Optional Parameters” in Speaking JavaScript ↩︎