Node Transform stream quirk when using Function.bind

Ran into this Transform stream behavior which I can't quite explain. First the setup:

var pass = new require('stream').PassThrough

var numbers = ['one:', 'two:', 'three:', 'four:', 'five:', 'six:', 'seven:', 'eight:', 'nine:', 'ten:']

pass.pipe(process.stdout)

OK, now when I write to the stream using a forEach, I get the following, expected, behavior:

numbers.forEach(function(val) {
  pass.write(val)
})

# one:two:three:four:five:six:seven:eight:nine:ten:

But! If I write to the stream in this ever so slightly different way, I get the following unexpected behavior:

numbers.forEach(pass.write.bind(pass))

# one:tthfoufivesix:seven:eight:nine:ten:

So my question is, why the different outputs? Am I just missing something here?

I'm running v0.10.30

Short answer:

.forEach passes multiple arguments to the callback, so when you pass the callback directly, you aren't just passing val to the callback, you are also passing the array index and the whole array to .write.

Long answer:

Your equivalence comparison is wrong:

numbers.forEach(pass.write.bind(pass));

is not the same as

numbers.forEach(function(val) {
  pass.write(val)
});

it is equivalent to this:

numbers.forEach(function() {
  pass.write.apply(pass, arguments);
});

or more specifically:

numbers.forEach(function(val, index, array) {
  pass.write(val, index /* encoding */, array /* cb */);
});

When you look at the signature of .write(chunk, encoding, cb), the callback is ignored if it isn't a function, so you are essentially doing .write(val, index). The crazy output arrises because those values get passed into Buffer before writing, so you are essentially calling this:

numbers.forEach(function(val, index, array) {
  pass.write(new Buffer(val, index /* encoding */));
});

that decomposes into something like this:

numbers.forEach(function(val, index, array) {
  var b = new Buffer(...);
  b.write(val, index /* encoding */);
  pass.write(b);
});

And that is where the magic happens. The signature of Buffer.prototype.write is

.write(string, [offset], [length], [encoding])

Since you passed the encoding as a number instead of an actual encoding, the call using the encoding value as the length instead of the encoding, so you are slicing up the string length based on the array index.