Streams API: Readable y Writable Streams
Aprende a procesar datos en tiempo real usando la API Streams, manejando flujos de datos con ReadableStream, WritableStream y TransformStream.
TL;DR - Resumen rápido
- Streams permiten procesar datos por fragmentos (chunks) en lugar de cargar todo en memoria
- ReadableStream representa una fuente de datos que puedes leer secuencialmente
- WritableStream representa un destino donde puedes escribir datos secuencialmente
- TransformStream permite modificar datos mientras fluyen de lectura a escritura
- response.body en fetch es un ReadableStream para procesar respuestas progresivamente
Introducción a Streams API
La Streams API es una interfaz moderna de JavaScript que te permite procesar datos por fragmentos (chunks) en lugar de cargar todo el contenido en memoria de una vez. Esto es especialmente útil para archivos grandes, videos, o cualquier dato que no necesitas tener completo antes de empezar a procesarlo.
Antes de Streams, cuando hacías una petición fetch, tenías que esperar a que toda la respuesta se descargara antes de poder procesarla. Con Streams, puedes empezar a procesar los datos mientras se descargan, lo que mejora el rendimiento y la experiencia del usuario, especialmente en dispositivos con memoria limitada.
- <strong>ReadableStream</strong>: Fuente de datos que puedes leer secuencialmente
- <strong>WritableStream</strong>: Destino donde puedes escribir datos secuencialmente
- <strong>TransformStream</strong>: Modifica datos mientras fluyen de lectura a escritura
- <strong>Chunk</strong>: Fragmento de datos que se procesa en cada iteración del stream
Soporte del navegador
Streams API es soportado por todos los navegadores modernos (Chrome 52+, Firefox 65+, Safari 10.1+, Edge 79+). La API está diseñada para ser asíncrona y usar Promesas, lo que la hace ideal para trabajar con fetch y otras operaciones asíncronas.
Readable Streams
Un ReadableStream es una fuente de datos que puedes leer secuencialmente. El stream tiene un método getReader() que devuelve un reader con el que puedes leer los datos chunk por chunk. Cada chunk es un Uint8Array que contiene una porción de los datos.
En este ejemplo, creamos un ReadableStream manualmente usando el constructor. El stream tiene un método start que recibe un controller. Usamoscontroller.enqueue() para enviar chunks al stream ycontroller.close() para indicar que no hay más datos. El reader usa read() para obtener cada chunk hasta que done es true.
Reader vs Stream
Un ReadableStream puede tener múltiples lectores, pero solo uno a la vez. Cuando creas un reader con getReader(), el stream se bloquea para otros lectores hasta que liberas el reader con releaseLock().
Response.body como ReadableStream
Una de las aplicaciones más útiles de Streams es con fetch. La propiedadresponse.body es un ReadableStream que te permite procesar la respuesta mientras se descarga. Esto es ideal para descargar archivos grandes, mostrar progreso de descarga, o procesar datos progresivamente.
Este ejemplo muestra cómo procesar una respuesta fetch mientras se descarga. Usamos response.body.getReader() para obtener un reader del stream de la respuesta. Luego leemos chunks iterativamente hasta que donees true, lo que indica que la descarga se completó. Esto es mucho más eficiente que esperar a que toda la respuesta se descargue.
Progreso de descarga
Puedes calcular el progreso de descarga dividiendo los bytes recibidos entre el tamaño total del archivo (Content-Length header). Esto te permite mostrar una barra de progreso real al usuario mientras se descarga el archivo.
Writable Streams
Un WritableStream es un destino donde puedes escribir datos secuencialmente. El stream tiene un método getWriter() que devuelve un writer con el que puedes enviar chunks al stream. Cada chunk se procesa de forma asíncrona y puedes esperar a que se complete con await writer.write().
Este ejemplo crea un WritableStream que recibe chunks y los procesa. El métodowrite del sink recibe cada chunk y puede procesarlo de forma asíncrona. El método close se llama cuando no hay más datos para escribir. El writer usa write() para enviar chunks y close()para finalizar el stream.
Backpressure
Los WritableStreams implementan backpressure para evitar que el escritor envíe datos más rápido de lo que el sink puede procesar. Usa await writer.readypara esperar a que el stream esté listo para aceptar más datos.
Transform Streams
Un TransformStream es un stream que modifica datos mientras fluyen de un ReadableStream a un WritableStream. Es ideal para procesar datos en tiempo real, como comprimir, descomprimir, o transformar el formato de los datos mientras se transfieren.
Este ejemplo crea un TransformStream que convierte todos los caracteres a mayúsculas. El método transform recibe cada chunk y puede modificarlo antes de enviarlo al writable stream. El método flush se llama cuando no hay más chunks para procesar. Los TransformStreams son especialmente útiles para procesar datos de forma incremental. JavaScript incluye streams integrados comoCompressionStream y DecompressionStream que puedes usar directamente para comprimir y descomprimir datos sin crear tu propio transform.
- <strong>Compresión</strong>: Usar CompressionStream para comprimir datos gzip
- <strong>Descompresión</strong>: Usar DecompressionStream para descomprimir datos
- <strong>Transformación</strong>: Modificar formato o contenido de datos
- <strong>Filtrado</strong>: Eliminar chunks que no cumplen ciertos criterios
- <strong>Agregación</strong>: Combinar múltiples chunks en uno
Casos de Uso Reales
Streams API tiene múltiples aplicaciones en el mundo real que mejoran el rendimiento y la experiencia del usuario. Aquí exploramos algunos de los casos más comunes donde procesar datos incrementalmente es esencial.
Este ejemplo muestra tres casos de uso reales: descarga de archivos grandes con progreso, procesamiento de JSON incremental, y compresión de datos con CompressionStream. En todos estos casos, procesar datos incrementalmente mejora el rendimiento y la experiencia del usuario, especialmente en dispositivos con memoria limitada.
JSON incremental
Para procesar JSON incrementalmente, puedes usar TextDecoder para decodificar chunks a texto y luego parsear el JSON. Para JSON ND (Newline Delimited), puedes parsear cada línea por separado a medida que llega.
Resumen: Streams API
Conceptos principales:
- •Streams permiten procesar datos por chunks en lugar de cargar todo en memoria
- •ReadableStream es una fuente de datos que puedes leer secuencialmente
- •WritableStream es un destino donde puedes escribir datos secuencialmente
- •TransformStream modifica datos mientras fluyen de lectura a escritura
- •response.body en fetch es un ReadableStream para procesar respuestas progresivamente
Mejores prácticas:
- •Usa streams para archivos grandes o datos que no necesitas completos
- •Implementa backpressure en writable streams para evitar sobrecarga
- •Usa TextDecoder para decodificar chunks de Uint8Array a texto
- •Cierra readers y writers cuando termines para liberar recursos
- •Usa TransformStream para procesar datos en tiempo real sin buffers grandes