Verkkotyöntekijät toiminnassa: miksi he ovat hyödyllisiä ja miten sinun pitäisi käyttää heitä

Javascript on yksisäikeinen, eikä useita komentosarjoja voida suorittaa samanaikaisesti. Joten jos suoritamme minkä tahansa raskaan laskentatehtävän, silloin joskus sivumme ei vastaa, eikä käyttäjä voi tehdä mitään muuta, ennen kuin suoritus on valmis.

Esimerkiksi:

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 */

Yllä olevassa esimerkissä, jos soitat keskiarvoon ennen hei- menetelmää, sivusi ei vastaa ja et voi napsauttaa Hei- kuvaketta, ennen kuin keskiarvon toteutus on valmis.

Voit nähdä, että kun keskiarvoa kutsutaan ensin syöttämällä 10000, se kesti ~ 1,82 sekuntia. Silloin sivu ei vastaa, etkä voinut napsauttaa hei-painiketta.

Asynkroninen ohjelmointi

Javascript antaa kehittäjille tavan kirjoittaa asynkronikoodia . Kirjoittamalla asynkronista koodia voit välttää tällaisen ongelman sovelluksessasi, koska se mahdollistaa sovelluksesi käyttöliittymän reagoimisen, "ajoittamalla" koodin osia suoritettavaksi hieman myöhemmin tapahtumasilmukassa.

Hyvä esimerkki XHR requestasynkronisesta ohjelmoinnista on , että tässä osumme sovellusliittymään asynkronisesti ja odottaessamme vastausta, muu koodi voidaan suorittaa. Mutta tämä rajoittuu tiettyihin käyttötapauksiin, jotka liittyvät lähinnä web-sovellusliittymiin.

Toinen tapa kirjoittaa asynkronikoodi on setTimeoutmenetelmä. Joissakin tapauksissa voit saavuttaa hyviä tuloksia poistamalla käyttöliittymän eston pidempään käynnissä olevista laskelmista käyttämällä setTimeout. Esimerkiksi jakamalla monimutkainen laskenta erillisissä setTimeoutpuheluissa.

Esimerkiksi:

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 !!') };

Tässä esimerkissä näet, että kun napsautat Laske keskiarvo -painiketta, voit silti napsauttaa Hei- painiketta (joka puolestaan ​​näyttää hälytysviestin). Tämä ohjelmointitapa on varmasti estämätön, mutta vie liikaa aikaa, eikä se ole toteutettavissa todellisissa sovelluksissa.

Tässä samalle tulolle 10000 kesti ~ 60 sekuntia, mikä on hyvin tehotonta.

Joten miten voimme ratkaista tällaiset ongelmat tehokkaasti?

Vastaus on verkkotyöntekijät.

Mitä verkkotyöntekijät ovat?

Javascriptin verkkotyöntekijät ovat hieno tapa suorittaa jokin erittäin työläs ja aikaa vievä tehtävä päälangasta erilliseen säikeeseen. Ne juoksevat taustalla ja suorittavat tehtäviä häiritsemättä käyttöliittymää.

Verkkotyöntekijät eivät ole osa JavaScriptiä, he ovat selainominaisuus, johon pääsee käsiksi JavaScriptin kautta.

Verkkotyöntekijät luodaan rakentajafunktiolla Worker (), joka suorittaa nimettyä JS-tiedostoa.

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

Jos määritetty tiedosto on olemassa, se ladataan asynkronisesti, ja jos ei, työntekijä epäonnistuu hiljaa, joten sovelluksesi toimii edelleen 404: n tapauksessa.

Opimme lisää verkkotyöntekijöiden luomisesta ja työskentelystä seuraavasta osiosta.

Työntekijän säikeellä on oma kontekstinsa, joten voit käyttää vain tiettyjä ominaisuuksia työntekijän säikeessä, kuten - verkkopistorasia, indeksoitu tietokanta.

Verkkotyöntekijöillä on joitain rajoituksia -

  1. Et voi suoraan manipuloida DOM: ää työntekijän sisältä.
  2. Et voi käyttää joitain ikkunaobjektin oletusmenetelmiä ja ominaisuuksia, koska ikkunaobjekti ei ole käytettävissä työntekijän säikeessä.
  3. Työläisketjun sisällä olevaan kontekstiin pääsee DedicatedWorkerGlobalScope- tai SharedWorkerGlobalScope-sovelluksen kautta käytön mukaan.

Verkkotyöntekijöiden ominaisuudet

Verkkotyöntekijöitä on kahta tyyppiä -

  1. Omistettu verkkotyöntekijä - Omaan työntekijään pääsee vain sen kutsun avulla.
  2. Jaettu verkkotyöntekijä - Jaettuun työntekijään pääsee käsiksi useilla skripteillä - vaikka heitä käytettäisiin eri ikkunoista, iframe-kehyksistä tai jopa työntekijöiltä.

Keskustelkaamme enemmän näistä kahdesta verkkotyöntekijästä -

Verkkotyöntekijän luominen

Luominen on suunnilleen sama sekä omistautuneelle että jaetulle verkkotyöntekijälle.

Omistettu verkkotyöntekijä

  • Uuden työntekijän luominen on helppoa, soita vain työntekijän konstruktorille ja välitä sen komentosarjan polku, jonka haluat suorittaa työntekijänä.
// create a dedicated web worker const myWorker = new Worker('worker.js');

Jaettu verkkotyöntekijä:

  • Uuden jaetun työntekijän luominen on suunnilleen sama kuin omistautuneen työntekijän, mutta eri rakentajan nimellä.
// creating a shared web worker const mySharedWorker = new SharedWorker('worker.js');

Viestintä pää- ja työntekijälangan välillä

Viestintä päälangan ja työntekijän langan välillä tapahtuu postMessage- menetelmän ja onmessage- tapahtumakäsittelijän kautta.

Omistettu verkkotyöntekijä

Omistetun verkkotyöntekijän tapauksessa viestintäjärjestelmä on yksinkertainen. Sinun tarvitsee vain käyttää postMessage-menetelmää aina, kun haluat lähettää viestin työntekijälle.

(() => { // 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); })();

And inside a web worker you can respond when the message is received by writing an event handler block like this:

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)}) };

The onmessage handler allows to run some code whenever a message is received.

Here we are calculating average of numbers and then use postMessage() again, to post the result back to the main thread.

As you can see on line 6 in main.js we have used onmessage event on the worker instance. So whenever worker thread use postMessage, onmessage in the main thread gets triggered.

  • Shared web worker

    In case of a shared web worker, communication system is little different. As one worker is shared between multiple scripts, we need to communicate via the port object of worker instance. This is done implicitly in case of dedicated workers. You need to use postMessage method whenever you want to send message to the worker.

(() => { // 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);

Inside a web worker (main-shared-worker.js) it is a little complex. First, we use an onconnect handler to fire code when a connection to the port happens (line 2).

We use the ports attribute of this event object to grab the port and store it in a variable (line 4).

Next, we add a message handler on the port to do the calculation and return the result to the main thread (line 7 and line 25) like this:

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)}) };

Termination of a web worker

If you need to immediately terminate a running worker from the main thread, you can do so by calling the worker’s terminate method:

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

The worker thread is killed immediately without an opportunity to complete its operations.

Spawning of web worker

Workers may spawn more workers if they wish. But they must be hosted within the same origin as the parent page.

Importing Scripts

Worker threads have access to a global function, importScripts(), which lets them import 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 */

Working Demo

We have discussed some of the approaches above to achieve async programming so that our UI doesn’t get blocked due to any heavy computational task. But there are some limitations to those approaches. So we can use web workers to solve these kind of problems efficiently.

Click here to run this live demo.

Here, you will see 3 sections:

  1. Blocking Code:

    When you click on calculate average, the loader does not display and after some time you see the final result and time taken. This is because as soon as the average method gets called, I have triggered the showLoader method also. But since JS is single threaded, it won’t execute showLoader until the execution of average gets completed. So, you won’t be able to see the loader in this case ever.

  2. Async Code:

    In this I tried to achieve the same functionality by using the setTimeout method and putting every function execution into an event loop. You will see the loader in this case, but the response takes time as compared to the method defined above.

  3. Web worker:

    This is an example of using a web worker. In this you will see the loader as soon as you click on calculate average and you will get a response in the same time as of method 1, for the same number.

You can access the source code for the same here.

Advanced concepts

There are some advanced concepts related to web workers. We won’t be discussing them in detail, but its good to know about them.

  1. Content Security Policy —

    Web workers have their own execution context independent of the document that created them and because of this reason they are not governed by the Content Security Policy of the parent thread/worker.

    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 —

    Voit myös upottaa työntekijän koodin verkkosivulle (html). Tätä varten sinun on lisättävä komentotunniste ilman src-määritettä ja määritettävä sille suoritettava MIME-tyyppi, kuten tämä:

    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 }    

Verkkotyöntekijöiden käyttämiseen sovelluksessamme on paljon käyttötapauksia. Olen juuri keskustellut pienestä skenaariosta. Toivottavasti tämä auttaa sinua ymmärtämään verkkotyöntekijöiden käsitteen.

[Linkit]

Github Repo: //github.com/bhushangoel/webworker-demo-1 Verkkotyöntekijä toiminnassa: //bhushangoel.github.io/webworker-demo-1/JS demonäyttely: //bhushangoel.github.io/

Kiitos, että luit.

Hyvää oppimista :)

Alun perin julkaistu osoitteessa www.thehungrybrain.com.