La API de Streams

 

 

 

Usando streams podemos recibir un recurso de la red, o de otras fuentes, y procesarlo tan pronto como llegue el primer bit.

 

Usando streams podemos recibir un recurso de la red, o de otras fuentes, y procesarlo tan pronto como llega el primer bit.

En lugar de esperar a que el recurso se descargue completamente antes de usarlo, podemos trabajar con él inmediatamente.

¿Qué es un stream?

El primer ejemplo que me viene a la mente es cargar un vídeo de YouTube: no es necesario cargarlo por completo antes de poder comenzar a verlo.

O transmisión en vivo, donde ni siquiera sabes cuándo terminará el contenido.

El contenido ni siquiera tiene por qué terminar, podría generarse indefinidamente.

La API de Streams

La API de Streams nos permite trabajar con este tipo de contenido.

Tenemos dos modos de transmisión diferentes: leer desde una transmisión y escribir en una transmisión.

Los flujos legibles están disponibles en todos los navegadores modernos excepto Internet Explorer.

Los flujos de escritura no están disponibles en Firefox e Internet Explorer.

Como siempre, consulte caniuse.com para obtener la información más actualizada sobre este asunto.

Empecemos con transmisiones legibles

Flujos legibles

Tenemos 3 clases de objetos cuando se trata de transmisiones legibles:

  • ReadableStream
  • ReadableStreamDefaultReader
  • ReadableStreamDefaultController

Podemos consumir transmisiones utilizando un objeto ReadableStream.

A continuación se muestra el primer ejemplo de un flujo legible. La API Fetch permite obtener un recurso de la red y ponerlo a disposición como flujo:

const stream = fetch('/resource').then((response) = response.body)

La bodypropiedad de la respuesta de búsqueda es una ReadableStreaminstancia de objeto. Esta es nuestra secuencia legible.

El lector

Al llamar getReader()a un ReadableStreamobjeto se devuelve un ReadableStreamDefaultReaderobjeto, el lector. Podemos obtenerlo de esta manera:

const reader = fetch('/resource').then((response) = response.body.getReader())

Leemos datos en fragmentos, donde un fragmento es un byte o una matriz tipificada. Los fragmentos se ponen en cola en el flujo y los leemos de a un fragmento por vez.

 

Una sola secuencia puede contener distintos tipos de fragmentos.

Una vez que tenemos un ReadableStreamDefaultReader objeto podemos acceder a los datos utilizando el read()método.

Tan pronto como se crea un lector, la transmisión se bloquea y ningún otro lector puede obtener fragmentos de ella hasta que releaseLock()lo llamemos.

Puedes hacer un tee en un arroyo para lograr este efecto, hablaremos más sobre esto más adelante.

Lectura de datos desde un flujo legible

Una vez que tenemos una ReadableStreamDefaultReaderinstancia de objeto, podemos leer datos de ella.

Así es como puedes leer el primer fragmento del flujo de contenido HTML de la página web flaviocopes.com, byte a byte (por razones CORS, puedes ejecutar esto en la ventana DevTools abierta en esa página web).

fetch('https://flaviocopes.com/').then((response) = { response.body .getReader() .read() .then(({ value, done }) = { console.log(value) })})

Si abres cada grupo de elementos de la matriz, accederás a los elementos individuales. Estos son bytes, almacenados en un Uint8Array:

Puede transformar esos bytes en caracteres utilizando la API de codificación :

const decoder = new TextDecoder('utf-8')fetch('https://flaviocopes.com/').then((response) = { response.body .getReader() .read() .then(({ value, done }) = { console.log(decoder.decode(value)) })})

que imprimirá los caracteres cargados en la página:

Esta nueva versión del código carga cada fragmento de la secuencia y lo imprime:

;(async () = { const fetchedResource = await fetch('https://flaviocopes.com/') const reader = await fetchedResource.body.getReader() let charsReceived = 0 let result = '' reader.read().then(function processText({ done, value }) { if (done) { console.log('Stream finished. Content received:') console.log(result) return } console.log(`Received ${result.length} chars so far!`) result += value return reader.read().then(processText) })})()

Envolví esto en una asyncfunción invocada inmediatamente para usar await.

La función processText() que creamos recibe un objeto con 2 propiedades.

  • doneverdadero si la transmisión finalizó y obtuvimos todos los datos
  • valueel valor del fragmento actual recibido

Creamos esta función recursiva para procesar todo el flujo.

Creando una secuencia

Advertencia: no compatible con Edge e Internet Explorer

Acabamos de ver cómo consumir un flujo legible generado por la API Fetch, lo cual es una excelente manera de comenzar a trabajar con flujos, ya que el caso de uso es práctico.

Ahora veamos cómo crear un flujo legible, para que podamos dar acceso a un recurso con nuestro código.

Ya hemos utilizado un ReadableStreamobjeto anteriormente. Ahora vamos a crear uno nuevo utilizando la newpalabra clave:

const stream = new ReadableStream()

Este flujo ya no es muy útil. Es un flujo vacío y, si alguien quiere leerlo, no hay datos.

 

Podemos definir cómo se comporta el flujo de datos al pasar un objeto durante la inicialización. Este objeto puede definir las siguientes propiedades:

  • startFunción que se llama cuando se crea la secuencia legible. Aquí se conecta a la fuente de datos y se realizan tareas administrativas.
  • pulluna función llamada repetidamente para obtener datos, mientras que no se alcanza el límite superior de la cola interna
  • canceluna función llamada cuando se cancela la transmisión, por ejemplo, cuando cancel()se llama al método en el extremo receptor

A continuación se muestra un ejemplo básico de la estructura del objeto:

const stream = new ReadableStream({ start(controller) {}, pull(controller) {}, cancel(reason) {},})

start()y pull()obtener un objeto controlador, es una instancia del ReadableStreamDefaultControllerobjeto, que le permite controlar el estado del flujo y la cola interna.

Para agregar datos al flujo, llamamos al controller.enqueue()método que pasa la variable que contiene nuestros datos:Te recomendamos la ruta dels origens

const stream = new ReadableStream({ start(controller) { controller.enqueue('Hello') },})

Cuando estemos listos para cerrar la transmisión, llamamos controller.close().

cancel()obtiene un reasonvalor que es una cadena proporcionada a la ReadableStream.cancel()invocación del método cuando se cancela la transmisión.

También podemos pasar un segundo objeto opcional que determina la estrategia de cola. Contiene 2 propiedades:

  • highWaterMarkla cantidad total de fragmentos que se pueden almacenar en la cola interna. Lo mencionamos cuando hablamos pull()sobre
  • size, un método que puedes usar para cambiar el tamaño del fragmento, expresado en bytes
{ highWaterMark, size()}

Estos son útiles principalmente para controlar la presión en la corriente, especialmente en el contexto de una cadena de tuberías , algo todavía experimental en las API web.

Cuando highWaterMarkse alcanza el valor de un flujo, se envía una señal de contrapresión a los flujos anteriores en la tubería para indicarles que reduzcan la presión de los datos.

Tenemos 2 objetos integrados que definen la estrategia de cola:

  • ByteLengthQueuingStrategyque espera hasta que el tamaño acumulado en bytes de los fragmentos supere el límite superior especificado
  • CountQueuingStrategyque espera hasta que el número acumulado de fragmentos supere la marca de agua alta especificada

Ejemplo de configuración de una marca de agua alta de 32 bytes:

new ByteLengthQueuingStrategy({ highWaterMark: 32 * 1024 }

Ejemplo de configuración de una marca de agua alta de 1 fragmento:

new CountQueuingStrategy({ highWaterMark: 1 })

Menciono esto para decirles que pueden controlar la cantidad de datos que fluyen en una transmisión y comunicarse con los otros actores, pero no entraremos en más detalles porque las cosas se complican bastante rápido.

Arroyos para pesca

Anteriormente mencioné que tan pronto como comenzamos a leer una transmisión, se bloquea y otros lectores no pueden acceder a ella hasta que releaseLock()lo llamemos.

 

Sin embargo, podemos duplicar la transmisión utilizando el tee()método en la transmisión misma:

const stream = //...const tees = stream.tee()

teesahora es una matriz que contiene 2 nuevos flujos, que puedes usar para leer usando tees[0]y tees[1].

Flujos de texto que se pueden escribir

Tenemos 3 clases de objetos cuando se trata de flujos escribibles:

  • WritableStream
  • WritableStreamDefaultReader
  • WritableStreamDefaultController

Podemos crear transmisiones que luego podamos consumir, utilizando un objeto WritableStream.

Así es como creamos un nuevo flujo escribible:

const stream = new WritableStream()

Para que sea útil, debemos pasar un objeto. Este objeto tendrá las siguientes implementaciones de métodos opcionales:

  • start()Se llama cuando se inicializa el objeto.
  • write()Se llama cuando un fragmento está listo para ser escrito en el receptor (la estructura subyacente que contiene los datos de transmisión antes de que se escriban)
  • close()Llamamos cuando terminamos de escribir los fragmentos.
  • abort()Se llama cuando queremos señalar un error.

Aquí hay un esqueleto:

const stream = new WritableStream({ start(controller) {}, write(chunk, controller) {}, close(controller) {}, abort(reason) {},})

start(), close()y write()pasa al controlador, una WritableStreamDefaultControllerinstancia de objeto.

En cuanto a ReadableStream(), podemos pasar un segundo objeto al new WritableStream()que se establece la estrategia de cola.

Por ejemplo, creemos un flujo que, dada una cadena almacenada en la memoria, crea un flujo al que los consumidores pueden conectarse.

Comenzamos definiendo un decodificador que usaremos para transformar los bytes que recibimos en caracteres usando el constructor de API de codificación TextDecoder() :

const decoder = new TextDecoder('utf-8')

Podemos inicializar el WritableStream que implementa el close()método, que se imprimirá en la consola cuando el mensaje se reciba completamente y el código del cliente lo llame:

const writableStream = new WritableStream({ write(chunk) { //... }, close() { console.log(`The message is ${result}`) },})

Comenzamos la write()implementación inicializando un ArrayBuffer y añadiéndole el fragmento. Luego procedemos a decodificar este fragmento, que es un byte, en un carácter utilizando el método decoder.decode() de la API de codificación. Luego añadimos este valor a una resultcadena que declaramos fuera de este objeto:

let resultconst writableStream = new WritableStream({ write(chunk) { const buffer = new ArrayBuffer(2) const view = new Uint16Array(buffer) view[0] = chunk const decoded = decoder.decode(view, { stream: true }) result += decoded }, close() { //... },})

El objeto WritableStream ahora está inicializado.

Ahora vamos e implementamos el código del cliente que utilizará esta secuencia.

Primero obtenemos el WritableStreamDefaultWriterobjeto del writableStreamobjeto:

const writer = writableStream.getWriter()

A continuación definimos el mensaje a enviar:

const message = 'Hello!'

Luego inicializamos el codificador para codificar los caracteres que queremos enviar al stream:

const encoder = new TextEncoder()const encoded = encoder.encode(message, { stream: true })

En este punto, la cadena se ha codificado en una matriz de bytes. Ahora, utilizamos el forEachbucle en esta matriz para enviar cada byte al flujo. Antes de cada llamada al write()método del escritor del flujo, verificamos la readypropiedad que devuelve una promesa, por lo que solo escribimos cuando el escritor del flujo está listo:

encoded.forEach((chunk) = { writer.ready.then(() = { return writer.write(chunk) })})

Lo único que nos falta ahora es cerrar el escritor. forEaches un bucle sincrónico, lo que significa que llegamos a este punto solo después de que se haya escrito cada elemento.

Todavía verificamos la readypropiedad, luego llamamos al método close():

writer.ready.then(() = { writer.close()})



Tal vez te puede interesar:

  1. Introducción a React
  2. Agregar evento de clic a los elementos DOM devueltos desde querySelectorAll
  3. Cómo cambiar el valor de un nodo DOM
  4. Cómo comprobar si un elemento DOM tiene una clase

La API de Streams

¿Qué es un stream?La API de StreamsFlujos legiblesEl lectorLectura de datos desde un flujo legibleCreando una secuenciaArroyos para pescaFlujos de texto que

programar

es

2025-01-20

 

Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

 

 

Update cookies preferences