Esta página está destinada a ser una introducción al trabajo con datos binarios en JavaScript. Bun implementa una serie de tipos de datos y utilidades para trabajar con datos binarios, la mayoría de los cuales son estándares Web. Cualquier API específica de Bun se indicará como tal.
A continuación se muestra una "hoja de trucos" rápida que también funciona como tabla de contenidos. Haz clic en un elemento de la columna izquierda para saltar a esa sección.
| Clase | Descripción |
|---|---|
TypedArray | Una familia de clases que proporcionan una interfaz tipo Array para interactuar con datos binarios. Incluye Uint8Array, Uint16Array, Int8Array y más. |
Buffer | Una subclase de Uint8Array que implementa una amplia gama de métodos de conveniencia. A diferencia de los otros elementos en esta tabla, esta es una API de Node.js (que Bun implementa). No se puede usar en el navegador. |
DataView | Una clase que proporciona una API get/set para escribir cierto número de bytes en un ArrayBuffer en un offset de bytes particular. Se usa a menudo para leer o escribir protocolos binarios. |
Blob | Un blob de solo lectura de datos binarios que generalmente representa un archivo. Tiene un MIME type, un size, y métodos para convertir a ArrayBuffer, ReadableStream y string. |
File | Una subclase de Blob que representa un archivo. Tiene una marca de tiempo name y lastModified. Hay soporte experimental en Node.js v20. |
BunFile | Solo Bun. Una subclase de Blob que representa un archivo cargado perezosamente en disco. Se crea con Bun.file(path). |
ArrayBuffer y vistas
Hasta 2009, no había una forma nativa del lenguaje para almacenar y manipular datos binarios en JavaScript. ECMAScript v5 introdujo una gama de nuevos mecanismos para esto. El bloque de construcción más fundamental es ArrayBuffer, una estructura de datos simple que representa una secuencia de bytes en memoria.
// este buffer puede almacenar 8 bytes
const buf = new ArrayBuffer(8);A pesar del nombre, no es un array y no admite ninguno de los métodos y operadores de array que uno podría esperar. De hecho, no hay forma de leer o escribir valores directamente desde un ArrayBuffer. Hay muy poco que puedas hacer con uno excepto verificar su tamaño y crear "rebanadas" de él.
const buf = new ArrayBuffer(8);
buf.byteLength; // => 8
const slice = buf.slice(0, 4); // devuelve un nuevo ArrayBuffer
slice.byteLength; // => 4Para hacer algo interesante necesitamos una construcción conocida como "vista". Una vista es una clase que envuelve una instancia de ArrayBuffer y te permite leer y manipular los datos subyacentes. Hay dos tipos de vistas: typed arrays y DataView.
DataView
La clase DataView es una interfaz de nivel inferior para leer y manipular los datos en un ArrayBuffer.
A continuación creamos un nuevo DataView y establecemos el primer byte en 3.
const buf = new ArrayBuffer(4);
// [0b00000000, 0b00000000, 0b00000000, 0b00000000]
const dv = new DataView(buf);
dv.setUint8(0, 3); // escribir valor 3 en el offset de byte 0
dv.getUint8(0); // => 3
// [0b00000011, 0b00000000, 0b00000000, 0b00000000]Ahora escribamos un Uint16 en el offset de byte 1. Esto requiere dos bytes. Estamos usando el valor 513, que es 2 * 256 + 1; en bytes, eso es 00000010 00000001.
dv.setUint16(1, 513);
// [0b00000011, 0b00000010, 0b00000001, 0b00000000]
console.log(dv.getUint16(1)); // => 513Ahora hemos asignado un valor a los primeros tres bytes en nuestro ArrayBuffer subyacente. Aunque el segundo y tercer byte se crearon usando setUint16(), aún podemos leer cada uno de sus bytes componentes usando getUint8().
console.log(dv.getUint8(1)); // => 2
console.log(dv.getUint8(2)); // => 1Intentar escribir un valor que requiere más espacio del disponible en el ArrayBuffer subyacente causará un error. A continuación intentamos escribir un Float64 (que requiere 8 bytes) en el offset de byte 0, pero solo hay cuatro bytes totales en el buffer.
dv.setFloat64(0, 3.1415);
// ^ RangeError: Out of bounds accessLos siguientes métodos están disponibles en DataView:
TypedArray
Los typed arrays son una familia de clases que proporcionan una interfaz tipo Array para interactuar con datos en un ArrayBuffer. Mientras que un DataView te permite escribir números de tamaño variable en un offset particular, un TypedArray interpreta los bytes subyacentes como un array de números, cada uno de tamaño fijo.
NOTE
Es común referirse a esta familia de clases colectivamente por su superclase compartida `TypedArray`. Esta clase es _interna_ a JavaScript; no puedes crear instancias de ella directamente, y `TypedArray` no está definida en el scope global. Piensa en ella como una `interface` o una clase abstracta.const buffer = new ArrayBuffer(3);
const arr = new Uint8Array(buffer);
// el contenido se inicializa en cero
console.log(arr); // Uint8Array(3) [0, 0, 0]
// asignar valores como un array
arr[0] = 0;
arr[1] = 10;
arr[2] = 255;
arr[3] = 255; // no-op, fuera de límitesMientras que un ArrayBuffer es una secuencia genérica de bytes, estas clases de typed array interpretan los bytes como un array de números de un tamaño de byte dado. La fila superior contiene los bytes crudos, y las filas posteriores contienen cómo se interpretarán estos bytes cuando se vean usando diferentes clases de typed array.
Las siguientes clases son typed arrays, junto con una descripción de cómo interpretan los bytes en un ArrayBuffer:
| Clase | Descripción |
|---|---|
Uint8Array | Cada uno (1) byte se interpreta como un entero sin signo de 8 bits. Rango 0 a 255. |
Uint16Array | Cada dos (2) bytes se interpretan como un entero sin signo de 16 bits. Rango 0 a 65535. |
Uint32Array | Cada cuatro (4) bytes se interpretan como un entero sin signo de 32 bits. Rango 0 a 4294967295. |
Int8Array | Cada uno (1) byte se interpreta como un entero con signo de 8 bits. Rango -128 a 127. |
Int16Array | Cada dos (2) bytes se interpretan como un entero con signo de 16 bits. Rango -32768 a 32767. |
Int32Array | Cada cuatro (4) bytes se interpretan como un entero con signo de 32 bits. Rango -2147483648 a 2147483647. |
Float16Array | Cada dos (2) bytes se interpretan como un número de punto flotante de 16 bits. Rango -6.104e5 a 6.55e4. |
Float32Array | Cada cuatro (4) bytes se interpretan como un número de punto flotante de 32 bits. Rango -3.4e38 a 3.4e38. |
Float64Array | Cada ocho (8) bytes se interpretan como un número de punto flotante de 64 bits. Rango -1.7e308 a 1.7e308. |
BigInt64Array | Cada ocho (8) bytes se interpretan como un BigInt con signo. Rango -9223372036854775808 a 9223372036854775807 (aunque BigInt es capaz de representar números más grandes). |
BigUint64Array | Cada ocho (8) bytes se interpretan como un BigInt sin signo. Rango 0 a 18446744073709551615 (aunque BigInt es capaz de representar números más grandes). |
Uint8ClampedArray | Igual que Uint8Array, pero automáticamente "limita" al rango 0-255 al asignar un valor a un elemento. |
La tabla a continuación demuestra cómo se interpretan los bytes en un ArrayBuffer cuando se ven usando diferentes clases de typed array.
| Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | Byte 7 | |
|---|---|---|---|---|---|---|---|---|
ArrayBuffer | 00000000 | 00000001 | 00000010 | 00000011 | 00000100 | 00000101 | 00000110 | 00000111 |
Uint8Array | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
Uint16Array | 256 (1 * 256 + 0) | 770 (3 * 256 + 2) | 1284 (5 * 256 + 4) | 1798 (7 * 256 + 6) | ||||
Uint32Array | 50462976 | 117835012 | ||||||
BigUint64Array | 506097522914230528n |
Para crear un typed array desde un ArrayBuffer predefinido:
// crear typed array desde ArrayBuffer
const buf = new ArrayBuffer(10);
const arr = new Uint8Array(buf);
arr[0] = 30;
arr[1] = 60;
// todos los elementos se inicializan en cero
console.log(arr); // => Uint8Array(10) [ 30, 60, 0, 0, 0, 0, 0, 0, 0, 0 ];Si intentáramos instanciar un Uint32Array desde este mismo ArrayBuffer, obtendríamos un error.
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf);
// ^ RangeError: ArrayBuffer length minus the byteOffset
// is not a multiple of the element sizeUn valor Uint32 requiere cuatro bytes (16 bits). Debido a que el ArrayBuffer tiene 10 bytes de longitud, no hay forma de dividir limpiamente su contenido en fragmentos de 4 bytes.
Para solucionar esto, podemos crear un typed array sobre una "rebanada" particular de un ArrayBuffer. El Uint16Array a continuación solo "ve" los primeros 8 bytes del ArrayBuffer subyacente. Para lograr esto, especificamos un byteOffset de 0 y una length de 2, que indica el número de números Uint32 que queremos que contenga nuestro array.
// crear typed array desde una rebanada de ArrayBuffer
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf, 0, 2);
/*
buf _ _ _ _ _ _ _ _ _ _ 10 bytes
arr [_______,_______] 2 elementos de 4 bytes
*/
arr.byteOffset; // 0
arr.length; // 2No necesitas crear explícitamente una instancia de ArrayBuffer; en su lugar, puedes especificar directamente una longitud en el constructor del typed array:
const arr2 = new Uint8Array(5);
// todos los elementos se inicializan en cero
// => Uint8Array(5) [0, 0, 0, 0, 0]Los typed arrays también se pueden instanciar directamente desde un array de números u otro typed array:
// desde un array de números
const arr1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
arr1[0]; // => 0;
arr1[7]; // => 7;
// desde otro typed array
const arr2 = new Uint8Array(arr);En términos generales, los typed arrays proporcionan los mismos métodos que los arrays regulares, con algunas excepciones. Por ejemplo, push y pop no están disponibles en typed arrays, porque requerirían redimensionar el ArrayBuffer subyacente.
const arr = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
// admite métodos comunes de array
arr.filter(n => n > 128); // Uint8Array(1) [255]
arr.map(n => n * 2); // Uint8Array(8) [0, 2, 4, 6, 8, 10, 12, 14]
arr.reduce((acc, n) => acc + n, 0); // 28
arr.forEach(n => console.log(n)); // 0 1 2 3 4 5 6 7
arr.every(n => n < 10); // true
arr.find(n => n > 5); // 6
arr.includes(5); // true
arr.indexOf(5); // 5Consulta la documentación de MDN para obtener más información sobre las propiedades y métodos de los typed arrays.
Uint8Array
Vale la pena destacar específicamente Uint8Array, ya que representa un "byte array" clásico: una secuencia de enteros sin signo de 8 bits entre 0 y 255. Este es el typed array más común que encontrarás en JavaScript.
En Bun, y algún día en otros motores JavaScript, tiene métodos disponibles para convertir entre byte arrays y representaciones serializadas de esos arrays como cadenas base64 o hex.
new Uint8Array([1, 2, 3, 4, 5]).toBase64(); // "AQIDBA=="
Uint8Array.fromBase64("AQIDBA=="); // Uint8Array(4) [1, 2, 3, 4, 5]
new Uint8Array([255, 254, 253, 252, 251]).toHex(); // "fffefdfcfb=="
Uint8Array.fromHex("fffefdfcfb"); // Uint8Array(5) [255, 254, 253, 252, 251]Es el valor de retorno de TextEncoder#encode, y el tipo de entrada de TextDecoder#decode, dos clases de utilidad diseñadas para traducir cadenas y varias codificaciones binarias, más notablemente "utf-8".
const encoder = new TextEncoder();
const bytes = encoder.encode("hello world");
// => Uint8Array(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]
const decoder = new TextDecoder();
const text = decoder.decode(bytes);
// => hello worldBuffer
Bun implementa Buffer, una API de Node.js para trabajar con datos binarios que es anterior a la introducción de typed arrays en la especificación de JavaScript. Desde entonces ha sido reimplementado como una subclase de Uint8Array. Proporciona una amplia gama de métodos, incluidos varios métodos tipo Array y tipo DataView.
const buf = Buffer.from("hello world");
// => Buffer(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]
buf.length; // => 11
buf[0]; // => 104, ascii para 'h'
buf.writeUInt8(72, 0); // => ascii para 'H'
console.log(buf.toString());
// => Hello worldPara documentación completa, consulta la documentación de Node.js.
Blob
Blob es una API Web comúnmente utilizada para representar archivos. Blob se implementó inicialmente en navegadores (a diferencia de ArrayBuffer que es parte de JavaScript mismo), pero ahora es compatible con Node y Bun.
No es común crear instancias de Blob directamente. Más a menudo, recibirás instancias de Blob desde una fuente externa (como un elemento <input type="file"> en el navegador) o una biblioteca. Dicho esto, es posible crear un Blob desde una o más "partes blob" de cadena o binarias.
const blob = new Blob(["<html>Hello</html>"], {
type: "text/html",
});
blob.type; // => text/html
blob.size; // => 19Estas partes pueden ser string, ArrayBuffer, TypedArray, DataView u otras instancias de Blob. Las partes blob se concatenan en el orden en que se proporcionan.
const blob = new Blob([
"<html>",
new Blob(["<body>"]),
new Uint8Array([104, 101, 108, 108, 111]), // "hello" en binario
"</body></html>",
]);El contenido de un Blob se puede leer de forma asíncrona en varios formatos.
await blob.text(); // => <html><body>hello</body></html>
await blob.bytes(); // => Uint8Array (copia el contenido)
await blob.arrayBuffer(); // => ArrayBuffer (copia el contenido)
await blob.stream(); // => ReadableStreamBunFile
BunFile es una subclase de Blob utilizada para representar un archivo cargado perezosamente en disco. Al igual que File, agrega una propiedad name y lastModified. A diferencia de File, no requiere que el archivo se cargue en memoria.
const file = Bun.file("index.txt");
// => BunFileFile
File es una subclase de Blob que agrega una propiedad name y lastModified. Se usa comúnmente en el navegador para representar archivos subidos mediante un elemento <input type="file">. Node.js y Bun implementan File.
// ¡en el navegador!
// <input type="file" id="file" />
const files = document.getElementById("file").files;
// => File[]const file = new File(["<html>Hello</html>"], "index.html", {
type: "text/html",
});Consulta la documentación de MDN para obtener información completa sobre la documentación.
Streams
Los streams son una abstracción importante para trabajar con datos binarios sin cargarlos todos en memoria a la vez. Se usan comúnmente para leer y escribir archivos, enviar y recibir solicitudes de red, y procesar grandes cantidades de datos.
Bun implementa las APIs Web ReadableStream y WritableStream.
NOTE
Bun también implementa el módulo `node:stream`, incluyendo [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams), [`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), y [`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). Para documentación completa, consulta los docs de Node.js.Para crear un stream simple:
const stream = new ReadableStream({
start(controller) {
controller.enqueue("hello");
controller.enqueue("world");
controller.close();
},
});El contenido de este stream se puede leer fragmento por fragmento con la sintaxis for await.
for await (const chunk of stream) {
console.log(chunk);
}
// => "hello"
// => "world"Para una discusión más completa de los streams en Bun, consulta API > Streams.
Conversión
Convertir de un formato binario a otro es una tarea común. Esta sección está destinada como referencia.
Desde ArrayBuffer
Dado que ArrayBuffer almacena los datos que subyacen a otras estructuras binarias como TypedArray, los fragmentos a continuación no están convirtiendo desde ArrayBuffer a otro formato. En cambio, están creando una nueva instancia usando los datos almacenados subyacentes.
A TypedArray
new Uint8Array(buf);A DataView
new DataView(buf);A Buffer
// crear Buffer sobre todo el ArrayBuffer
Buffer.from(buf);
// crear Buffer sobre una rebanada del ArrayBuffer
Buffer.from(buf, 0, 10);A string
Como UTF-8:
new TextDecoder().decode(buf);A number[]
Array.from(new Uint8Array(buf));A Blob
new Blob([buf], { type: "text/plain" });A ReadableStream
El siguiente fragmento crea un ReadableStream y pone en cola todo el ArrayBuffer como un único fragmento.
new ReadableStream({
start(controller) {
controller.enqueue(buf);
controller.close();
},
});Con fragmentación">
Para hacer stream del ArrayBuffer en fragmentos, usa una vista Uint8Array y pon en cola cada fragmento.
const view = new Uint8Array(buf);
const chunkSize = 1024;
new ReadableStream({
start(controller) {
for (let i = 0; i < view.length; i += chunkSize) {
controller.enqueue(view.slice(i, i + chunkSize));
}
controller.close();
},
});Desde TypedArray
A ArrayBuffer
Esto recupera el ArrayBuffer subyacente. Ten en cuenta que un TypedArray puede ser una vista de una rebanada del buffer subyacente, por lo que los tamaños pueden diferir.
arr.buffer;A DataView
Para crear un DataView sobre el mismo rango de bytes que el TypedArray.
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);A Buffer
Buffer.from(arr);A string
Como UTF-8:
new TextDecoder().decode(arr);A number[]
Array.from(arr);A Blob
// solo si arr es una vista de todo su TypedArray de respaldo
new Blob([arr.buffer], { type: "text/plain" });A ReadableStream
new ReadableStream({
start(controller) {
controller.enqueue(arr);
controller.close();
},
});Con fragmentación">
Para hacer stream del ArrayBuffer en fragmentos, divide el TypedArray en fragmentos y pon en cola cada uno individualmente.
new ReadableStream({
start(controller) {
for (let i = 0; i < arr.length; i += chunkSize) {
controller.enqueue(arr.slice(i, i + chunkSize));
}
controller.close();
},
});Desde DataView
A ArrayBuffer
view.buffer;A TypedArray
Solo funciona si el byteLength del DataView es un múltiplo del BYTES_PER_ELEMENT de la subclase TypedArray.
new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
new Uint16Array(view.buffer, view.byteOffset, view.byteLength / 2);
new Uint32Array(view.buffer, view.byteOffset, view.byteLength / 4);
// etc...A Buffer
Buffer.from(view.buffer, view.byteOffset, view.byteLength);A string
Como UTF-8:
new TextDecoder().decode(view);A number[]
Array.from(view);A Blob
new Blob([view.buffer], { type: "text/plain" });A ReadableStream
new ReadableStream({
start(controller) {
controller.enqueue(view.buffer);
controller.close();
},
});Con fragmentación">
Para hacer stream del ArrayBuffer en fragmentos, divide el DataView en fragmentos y pon en cola cada uno individualmente.
new ReadableStream({
start(controller) {
for (let i = 0; i < view.byteLength; i += chunkSize) {
controller.enqueue(view.buffer.slice(i, i + chunkSize));
}
controller.close();
},
});Desde Buffer
A ArrayBuffer
buf.buffer;A TypedArray
new Uint8Array(buf);A DataView
new DataView(buf.buffer, buf.byteOffset, buf.byteLength);A string
Como UTF-8:
buf.toString();Como base64:
buf.toString("base64");Como hex:
buf.toString("hex");A number[]
Array.from(buf);A Blob
new Blob([buf], { type: "text/plain" });A ReadableStream
new ReadableStream({
start(controller) {
controller.enqueue(buf);
controller.close();
},
});Con fragmentación">
Para hacer stream del ArrayBuffer en fragmentos, divide el Buffer en fragmentos y pon en cola cada uno individualmente.
new ReadableStream({
start(controller) {
for (let i = 0; i < buf.length; i += chunkSize) {
controller.enqueue(buf.slice(i, i + chunkSize));
}
controller.close();
},
});Desde Blob
A ArrayBuffer
La clase Blob proporciona un método de conveniencia para este propósito.
await blob.arrayBuffer();A TypedArray
await blob.bytes();A DataView
new DataView(await blob.arrayBuffer());A Buffer
Buffer.from(await blob.arrayBuffer());A string
Como UTF-8:
await blob.text();A number[]
Array.from(await blob.bytes());A ReadableStream
blob.stream();Desde ReadableStream
Es común usar Response como una representación intermedia conveniente para facilitar la conversión de ReadableStream a otros formatos.
stream; // ReadableStream
const buffer = new Response(stream).arrayBuffer();Sin embargo, este enfoque es verboso y agrega sobrecarga que ralentiza innecesariamente el rendimiento general. Bun implementa un conjunto de funciones de conveniencia optimizadas para convertir ReadableStream a varios formatos binarios.
A ArrayBuffer
// con Response
new Response(stream).arrayBuffer();
// con función de Bun
Bun.readableStreamToArrayBuffer(stream);A Uint8Array
// con Response
new Response(stream).bytes();
// con función de Bun
Bun.readableStreamToBytes(stream);A TypedArray
// con Response
const buf = await new Response(stream).arrayBuffer();
new Int8Array(buf);
// con función de Bun
new Int8Array(Bun.readableStreamToArrayBuffer(stream));A DataView
// con Response
const buf = await new Response(stream).arrayBuffer();
new DataView(buf);
// con función de Bun
new DataView(Bun.readableStreamToArrayBuffer(stream));A Buffer
// con Response
const buf = await new Response(stream).arrayBuffer();
Buffer.from(buf);
// con función de Bun
Buffer.from(Bun.readableStreamToArrayBuffer(stream));A string
Como UTF-8:
// con Response
await new Response(stream).text();
// con función de Bun
await Bun.readableStreamToText(stream);A number[]
// con Response
const arr = await new Response(stream).bytes();
Array.from(arr);
// con función de Bun
Array.from(new Uint8Array(Bun.readableStreamToArrayBuffer(stream)));Bun proporciona una utilidad para resolver un ReadableStream a un array de sus fragmentos. Cada fragmento puede ser una cadena, typed array o ArrayBuffer.
// con función de Bun
Bun.readableStreamToArray(stream);A Blob
new Response(stream).blob();A ReadableStream
Para dividir un ReadableStream en dos streams que se pueden consumir independientemente:
const [a, b] = stream.tee();