Trabajadores web en acción: por qué son útiles y cómo debería utilizarlos

Javascript es de un solo hilo y no se pueden ejecutar varios scripts al mismo tiempo. Entonces, si ejecutamos una tarea de cálculo pesada, a veces nuestra página deja de responder y el usuario no puede hacer nada más hasta que se complete la ejecución.

Por ejemplo:

average = (numbers) => { let startTime = new Date().getTime(); let len = numbers, sum = 0, i; if (len === 0) { return 0; } for (i = 0; i  { alert("Hello World !!"); } /* Paste the above code in browser dev tool console and try to call average(10000) and hello one by one */

En el ejemplo anterior, si llama a average antes del método hello , su página dejará de responder y no podrá hacer clic en Hello hasta que se complete la ejecución de average .

Puede ver que cuando se llama a average con 10000 como entrada primero, tomó ~ 1.82 segundos. Durante ese período de tiempo, la página deja de responder y no pudo hacer clic en el botón de saludo.

Programación asincrónica

Javascript permite a los desarrolladores escribir código asíncrono . Al escribir código asincrónico, puede evitar este tipo de problema en su aplicación, ya que permite que la interfaz de usuario de su aplicación sea receptiva, al "programar" partes del código para que se ejecuten un poco más tarde en el ciclo de eventos.

Un buen ejemplo de programación asíncrona es XHR request, en esto, accedemos a una API de forma asincrónica y mientras esperamos la respuesta, se puede ejecutar otro código. Pero esto se limita principalmente a ciertos casos de uso relacionados con las API web.

Otra forma de escribir código asincrónico es usando setTimeoutmétodo. En algunos casos, puede lograr buenos resultados al desbloquear la interfaz de usuario de cálculos de ejecución más larga utilizando setTimeout. Por ejemplo, agrupando un cálculo complejo en setTimeoutllamadas separadas .

Por ejemplo:

average = (numbers) => { let startTime = new Date().getTime(); var len = numbers, sum = 0, i; if (len === 0) { return 0; } let calculateSumAsync = (i) => { if (i  { sum += i; calculateSumAsync(i + 1); }, 0); } else { // The end of the array is reached so we're invoking the alert. let endTime = new Date().getTime(); alert('Average - ', sum / len); } }; calculateSumAsync(0); }; hello = () => { alert('Hello World !!') };

En este ejemplo, puede ver que después de hacer clic en el botón Calcular promedio , aún puede hacer clic en el botón Hola (que a su vez muestra un mensaje de alerta). Esta forma de programación seguramente no es bloqueante, pero lleva demasiado tiempo y no es factible en aplicaciones del mundo real.

Aquí, para la misma entrada 10000, tomó ~ 60 segundos, lo cual es muy ineficiente.

Entonces, ¿cómo resolvemos este tipo de problemas de manera eficiente?

La respuesta es Web Workers.

¿Qué son los trabajadores web?

Los trabajadores web en Javascript son una excelente manera de ejecutar alguna tarea que es muy laboriosa y toma tiempo en un hilo separado del hilo principal. Se ejecutan en segundo plano y realizan tareas sin interferir con la interfaz de usuario.

Los Web Workers no son parte de JavaScript, son una función del navegador a la que se puede acceder a través de JavaScript.

Los trabajadores web son creados por una función constructora Worker () que ejecuta un archivo JS con nombre.

// create a dedicated web worker const myWorker = new Worker('worker.js');

Si el archivo especificado existe, se descargará de forma asincrónica y, de lo contrario, el trabajador fallará silenciosamente, por lo que su aplicación seguirá funcionando en el caso de 404.

Aprenderemos más sobre la creación y el funcionamiento de los trabajadores web en la siguiente sección.

El hilo de trabajo tiene su propio contexto y, por lo tanto, solo puede acceder a funciones seleccionadas dentro de un hilo de trabajo como: sockets web, base de datos indexada.

Existen algunas restricciones con los trabajadores web:

  1. No puede manipular directamente el DOM desde el interior de un trabajador.
  2. No puede utilizar algunos métodos y propiedades predeterminados del objeto de ventana, ya que el objeto de ventana no está disponible dentro de un hilo de trabajo.
  3. Se puede acceder al contexto dentro del hilo de trabajo a través de DedicatedWorkerGlobalScope o SharedWorkerGlobalScope, según el uso.

Características de los trabajadores web

Hay dos tipos de trabajadores web:

  1. Trabajador web dedicado: solo se puede acceder a un trabajador dedicado mediante el script que lo llamó.
  2. Trabajador web compartido: se puede acceder a un trabajador compartido mediante varios scripts, incluso si se accede a ellos desde diferentes ventanas, iframes o incluso trabajadores.

Analicemos más sobre esos dos tipos de trabajadores web:

Creación de un trabajador web

La creación es prácticamente la misma para un trabajador web dedicado y compartido.

Trabajador web dedicado

  • Crear un nuevo trabajador es simple, simplemente llame al constructor del trabajador y pase la ruta del script que desea ejecutar como trabajador.
// create a dedicated web worker const myWorker = new Worker('worker.js');

Trabajador web compartido:

  • La creación de un nuevo trabajador compartido es prácticamente igual que la de un trabajador dedicado, pero con un nombre de constructor diferente.
// creating a shared web worker const mySharedWorker = new SharedWorker('worker.js');

Comunicación entre el hilo principal y el trabajador

La comunicación entre el hilo principal y subproceso de trabajo pasa a través de postMessage método y onmessage controlador de eventos.

Trabajador web dedicado

En el caso de un trabajador web dedicado, el sistema de comunicación es simple. Solo necesita usar el método postMessage siempre que desee enviar un mensaje al trabajador.

(() => { // new worker let myWorker = new Worker('worker.js'); // event handler to recieve message from worker myWorker.onmessage = (e) => { document.getElementById('time').innerHTML = `${e.data.time} seconds`; }; let average = (numbers) => { // sending message to web worker with an argument myWorker.postMessage(numbers); } average(1000); })();

Y dentro de un trabajador web, puede responder cuando se recibe el mensaje escribiendo un bloque de controlador de eventos como este:

onmessage = (e) => { let numbers = e.data; let startTime = new Date().getTime(); let len = numbers, sum = 0, i; if (len === 0) { return 0; } for (i = 0; i < len; i++) { sum += i; } let endTime = new Date().getTime(); postMessage({average: sum / len, time: ((endTime - startTime) / 1000)}) };

El onmessagecontrolador permite ejecutar algún código cada vez que se recibe un mensaje.

Aquí estamos calculando el promedio de números y luego lo usamos postMessage()nuevamente, para publicar el resultado en el hilo principal.

Como puede ver en la línea 6 en main.js , hemos usado el evento onmessage en la instancia del trabajador. Entonces, cada vez que el hilo de trabajo usa postMessage, se activa un mensaje en el hilo principal.

  • Trabajador web compartido

    En el caso de un trabajador web compartido, el sistema de comunicación es un poco diferente. Como un trabajador se comparte entre varios scripts, necesitamos comunicarnos a través del objeto de puerto de la instancia del trabajador. Esto se hace implícitamente en el caso de trabajadores dedicados. Debe utilizar el método postMessage siempre que desee enviar un mensaje al trabajador.

(() => { // new worker let myWorker = new Worker('worker.js'); // event handler to recieve message from worker myWorker.onmessage = (e) => { document.getElementById('time').innerHTML = `${e.data.time} seconds`; }; let average = (numbers) => { // sending message to web worker with an argument myWorker.postMessage(numbers); } average(1000);

Dentro de un trabajador web ( main-shared-worker.js ) es un poco complejo. Primero, usamos un onconnectcontrolador para activar el código cuando ocurre una conexión con el puerto ( línea 2 ).

Usamos el portsatributo de este objeto de evento para tomar el puerto y almacenarlo en una variable ( línea 4 ).

A continuación, agregamos un messagecontrolador en el puerto para hacer el cálculo y devolver el resultado al hilo principal ( línea 7 y línea 25 ) así:

onmessage = (e) => { let numbers = e.data; let startTime = new Date().getTime(); let len = numbers, sum = 0, i; if (len === 0) { return 0; } for (i = 0; i < len; i++) { sum += i; } let endTime = new Date().getTime(); postMessage({average: sum / len, time: ((endTime - startTime) / 1000)}) };

Despido de un trabajador web

Si necesita terminar inmediatamente a un trabajador en ejecución desde el hilo principal, puede hacerlo llamando al método de terminación del trabajador :

// terminating a web worker instance myWorker.terminate();

El hilo de trabajo se mata inmediatamente sin la oportunidad de completar sus operaciones.

Desove del trabajador web

Los trabajadores pueden generar más trabajadores si lo desean. Pero deben alojarse dentro del mismo origen que la página principal.

Importación de secuencias de comandos

Los hilos de trabajo tienen acceso a una función global importScripts(), que les permite importar scripts.

importScripts(); /* imports nothing */ importScripts('foo.js'); /* imports just "foo.js" */ importScripts('foo.js', 'bar.js'); /* imports two scripts */ importScripts('//example.com/hello.js'); /* You can import scripts from other origins */

Demo de trabajo

Hemos discutido algunos de los enfoques anteriores para lograr la programación asincrónica de modo que nuestra interfaz de usuario no se bloquee debido a cualquier tarea computacional pesada. Pero existen algunas limitaciones para esos enfoques. Por lo tanto, podemos utilizar trabajadores web para resolver este tipo de problemas de manera eficiente.

Haga clic aquí para ejecutar esta demostración en vivo.

Aquí, verá 3 secciones:

  1. Código de bloqueo :

    Cuando haces clic en calcular promedio , el cargador no se muestra y después de un tiempo ves el resultado final y el tiempo empleado. Esto se debe a que tan pronto como se llama al método promedio , también activé el método showLoader . Pero dado que JS es de un solo subproceso, no ejecutará showLoader hasta que se complete la ejecución del promedio. Por lo tanto, nunca podrá ver el cargador en este caso.

  2. Código asincrónico :

    En esto, traté de lograr la misma funcionalidad usando el método setTimeout y poniendo cada ejecución de función en un bucle de eventos. Verá el cargador en este caso, pero la respuesta lleva tiempo en comparación con el método definido anteriormente.

  3. Trabajador web :

    Este es un ejemplo del uso de un trabajador web. En este verá el cargador en cuanto haga clic en calcular promedio y obtendrá una respuesta en el mismo tiempo que en el método 1, para el mismo número.

Puede acceder al código fuente del mismo aquí.

Conceptos avanzados

Hay algunos conceptos avanzados relacionados con los trabajadores web. No los discutiremos en detalle, pero es bueno conocerlos.

  1. Política de seguridad de contenido:

    Los trabajadores web tienen su propio contexto de ejecución independiente del documento que los creó y, por esta razón, no se rigen por la Política de seguridad de contenido del hilo / trabajador principal.

    The exception to this is if the worker script's origin is a globally unique identifier (for example, if its URL has a scheme of data or blob). In this case, the worker inherit the content security policy of the document or worker that created it.

  2. Transferring data to and from workers

    Data passed between main and worker thread is copied and not shared. Objects are serialized as they're handed to the worker, and subsequently, de-serialized on the other end. The page and worker do not share the same instance, so the end result is that a duplicate is created on each end.

    Browsers implemented Structured Cloning algorithm to achieve this.

  3. Embedded workers —

    You can also embed the code of worker inside a web page (html). For this you need to add a script tag without a src attribute and assign a non-executable MIME type to it, like this:

    embedded worker   // This script WON'T be parsed by JS engines because its MIME type is text/js-worker. var myVar = 'Hello World!'; // worker block function onmessage(e) { // worker code }    

There are a lot of use cases to use web workers in our application. I have just discussed a small scenario. Hope this helps you understand the concept of web workers.

[Links]

Github Repo : //github.com/bhushangoel/webworker-demo-1 Web worker in action : //bhushangoel.github.io/webworker-demo-1/JS demo showcase : //bhushangoel.github.io/

Thank you for reading.

Happy Learning :)

Originally published at www.thehungrybrain.com.