Search code examples
node.jstypescriptbuffer

A use of Symbol.species on the Node.js Buffer class to create a FastBuffer that doesn't make much sense


I came across the following code looking at the source of ws (a popular WebSocket implementation for Node.js):

const FastBuffer = Buffer[Symbol.species];

So, how do they use this FastBuffer? Well, no custom implementations or any added code, they just use it to make Buffer instances from other buffers:

this._buffers[0] = new FastBuffer(
  buf.buffer,
  buf.byteOffset + n,
  buf.length - n
);
return new FastBuffer(buf.buffer, buf.byteOffset, n);

It is well known that the Buffer constructors are deprecated, but aside from that I couldn't even find a constructor definition that receives an offset and a length like the one that ws seems to be using.

What is this code doing under the hood to access that constructor? How does this add performance? Is this documented somewhere?


Solution

  • What is this code doing under the hood to access that constructor?

    Let's start with the purpose of the Symbol.species/@@species pattern: It allows subclasses to define what constructor should be used to create new instances. You would often use it on an instance you wanted to copy (someObject.constructor[Symbol.species]), but you can use it on the class directly.

    Looking at what it returns, it's the FastBuffer class that Node.js uses internally (those line numbers will rot), which is a class inheriting directly from Uint8Array (not from Buffer).

    The fact that Buffer supports @@species is not documented, so the code you're quoting would appear to be hooking into and relying on Node.js internals.

    How does this add performance?

    I can't point to something specific, but presumably the Node.js implementers called it FastBuffer for a reason. :-) Probably by not being a full Buffer it can have optimizations that Buffer can't have.

    Is this documented somewhere?

    It's hard to prove a negative, but the @@species on Buffer isn't, nor does a search for FastBuffer on the Node.js API docs turn anything up.

    It is well known that the Buffer constructors are deprecated, but aside from that I couldn't even find a constructor definition that receives an offset and a length like the one that ws seems to be using.

    Since what we're getting back isn't necessarily a Buffer, it doesn't really matter, but Buffer does have that constructor. But more importantly, the (internal?) FastBuffer class supports that signature, as does its parent class Uint8Array.