My code implementation is the following extension of the Uint8Array class.
export class ByteArray extends Uint8Array {
...
private _encoded: string;
...
constructor(_encoded: string) {
super(Buffer.from(_encoded, "base64"));
this._encoded = _encoded;
}
}
I am getting the error:
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received type number (134)
Whenever I try to use this.slice(start, end)
. For example this.slice(4, 10);
. Any help is greatly appreciated.
The problem is caused by your constructor and how it gets called by the implementation of .slice()
for the Uint8Array
. The definition of that .slice()
method says that it will create a new TypedArray
with a new Buffer under it and to do that, it calls the constructor of the object that the .slice()
was called on. That would be the constructor of your ByteArray
class since the object is a ByteArray
. So, it calls your constructor for the ByteArray
class.
But, your constructor does not support the form of the Uint8Array
constructor that .slice()
tries to use. The Uint8Array
constructor supports all of these forms:
new Uint8Array(); // new in ES2017
new Uint8Array(length);
new Uint8Array(typedArray);
new Uint8Array(object);
new Uint8Array(buffer);
new Uint8Array(buffer, byteOffset);
new Uint8Array(buffer, byteOffset, length);
Probably .slice()
is using this last form which would cause your constructor to call:
super(Buffer.from(_encoded, "base64"));
But, _encoded
in this case would be a buffer object, not the string that Buffer.from(_encoded, "base64")
is expecting and thus the error you get.
There are a couple ways to fix this:
Detect Your Constructor Arguments, Pass Others Through
You can fix it by passing through any constructor arguments that aren't just a single string to the regular constructor. But, this will create an object of your ByteArray class that does NOT have the _encoded
property set on it. I'm not sure what you really want to do with the object that .slice()
returns.
Here's a version that works detects your particular constructor and otherwise passes things onto the Uint8Array
constructor (normal Javascript, not TypeScript):
class ByteArray extends Uint8Array {
constructor(...args) {
let _encoded = args[0];
// see if constructor arguments are my string argument
if (typeof _encoded === "string") {
super(Buffer.from(_encoded, "base64"));
this._encoded = _encoded;
} else {
// not my string, pass through all constructor arguments
// to default constructor
super(...args);
}
}
}
Note: This is a general issue when you sub-class an object that contains methods that create new versions of itself. Those methods will attempt to use the constructor of the existing object type and will then call that constructor to create the new object. Your constructor MUST support existing forms of constructor arguments for this to work because you don't know which form of constructor arguments these other methods may be using.
Tell Uint8Array Methods to Create Uint8Array Objects
Another way to fix this is to specify that you don't want .slice()
or any other method called on your ByteArray
to create a ByteArray
object, but rather to create a Uint8Array
. You can do that like this:
class ByteArray extends Uint8Array {
// when creating new objects from methods of this one,
// make them regular Uint8Array objects, not ByteArray objects
static get[Symbol.species]() { return Uint8Array; }
constructor(_encoded) {
super(Buffer.from(_encoded, "base64"));
}
}
Then, when .slice()
goes to create a new object, it will create a Uint8Array
instead of your ByteArray
and the constructor for Uint8Array
will work normally. This means that the result of call .slice()
on one of your ByteArray
objects will not be a ByteArray
object - it will be a Uint8Array
instead.
You can read about the Symbol.species
property here on MDN.
General Discussion
The problem you ran into is a problem for sub-classing any object that has methods that attempt to create a new object of the same class as the one they were called on. For example, sub-classing an Array
object would have the same issue with .slice()
. To support full functionality of all these base class methods, you have to either support all the constructor arguments that the base class methods may use or you have to use the .species
support to tell those base class methods that you don't want them to create objects of your sub-class, but rather of the base-class (so they won't call your constructor).