For the sake of maintainability, I would not add a method to Function to achieve this. The other answers to the question here contain two implementation of Function.prototype.bindAppend that are incompatible with each other. So imagine creating your own bindAppend and then having to use your application with a body of code whose author decided to do the same thing but uses a implementation incompatible with yours.
Polyfills are fine insofar as they aim to fill in a missing function according to a specific standard. A polyfill for Function.prototype.bind, for instance, is not free to deviate from ECMA-262, 5th edition. If it does deviate, then it is arguably "buggy" and should be replaced with an implementation that does not deviate from the standard. When one implements methods that are not defined by a standard there's no such constraint.
The following benchmarking suite shows the difference between various ways of attaining the result in the question.
var async = require("async");
var Benchmark = require("benchmark");
var suite = new Benchmark.Suite();
function dump (err, something) {
console.log(arguments);
console.log(err, something);
}
function addToSuite(name, makeFinished) {
function func(something, callback) {
async.eachSeries([1,2,3],
function iterator(item, asyncCallback) {
var err;
// do stuff
asyncCallback(err || null);
},
makeFinished(this, something, callback)
);
}
console.log(name, "dump");
func("foo", dump);
console.log("");
suite.add(name, function () {
func("foo", function (err, something) {});
});
}
// Taken from mpm's http://stackoverflow.com/a/23670553/1906307
Function.prototype.bindAppend = function(context) {
var func = this;
var args = [].slice.call(arguments).slice(1);
return function() {
return func.apply(context, [].slice.call(arguments).concat(args));
};
};
addToSuite("anonymous function", function (context, something, callback) {
return function finished(err) {
callback(err, something);
};
});
addToSuite("mpm's bindAppend", function (context, something, callback) {
return callback.bindAppend(context, something);
});
addToSuite("addErr, only one parameter", function (context, something,
callback) {
function addErr(f, something) {
return function (err) {
return f(err, something);
};
}
return addErr(callback, something);
});
addToSuite("addErr, multi param", function (context, something, callback) {
function addErr(f, one, two, three, four, five, six) {
return function (err) {
return f(err, one, two, three, four, five, six);
};
}
return addErr(callback, something);
});
addToSuite("addErr, switch", function (context, something, callback) {
function addErr(f, one, two, three, four, five, six) {
var args = arguments;
return function (err) {
switch(args.length) {
case 1: return f(err);
case 2: return f(err, one);
case 3: return f(err, one, two);
case 4: return f(err, one, two, three);
case 5: return f(err, one, two, three, four);
case 6: return f(err, one, two, three, four, five);
case 6: return f(err, one, two, three, four, five, six);
default: throw Error("unsupported number of args");
}
};
}
return addErr(callback, something);
});
suite
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('The fastest is ' + this.filter('fastest').pluck('name'));
})
.run();
Output:
anonymous function dump
{ '0': undefined, '1': 'foo' }
undefined 'foo'
mpm's bindAppend dump
{ '0': 'foo' }
foo undefined
addErr, only one parameter dump
{ '0': undefined, '1': 'foo' }
undefined 'foo'
addErr, multi param dump
{ '0': undefined,
'1': 'foo',
'2': undefined,
'3': undefined,
'4': undefined,
'5': undefined,
'6': undefined }
undefined 'foo'
addErr, switch dump
{ '0': undefined, '1': 'foo' }
undefined 'foo'
anonymous function x 4,137,843 ops/sec ±3.18% (93 runs sampled)
mpm's bindAppend x 663,044 ops/sec ±1.42% (97 runs sampled)
addErr, only one parameter x 3,944,633 ops/sec ±1.89% (91 runs sampled)
addErr, multi param x 3,209,292 ops/sec ±2.57% (84 runs sampled)
addErr, switch x 3,087,979 ops/sec ±2.00% (91 runs sampled)
The fastest is anonymous function
Notes:
The dump calls dump to screen what it is the final callback gets when there is no error whatsoever.
When there is no error, mpm's bindAppend implementation will call callback with only one parameter that has the value "foo". This is a very different behavior from the original function finished(err) { callback(err,something);} callback it is meant to replace.
Everything is faster than mpm's bindAppend.
I would not use the addErr, multi param implementation. I put it there to check how that approach would perform.
The addErr functions are faster than bindAppend, at the cost of flexibility. They add only one parameter to the callback.
At the end of the day, I'd most likely use the addErr, switch implementation with a large enough number of cases to handle the needs of my code. If I needed something which would have absolute flexibility, for some limited cases, I'd use something similar to bindAppend but not as a method on Function.