Az internetes dolgozók működés közben: miért segítőkészek és hogyan kell használni őket

A Javascript egyszálú, és több parancsfájl nem hajtható végre egyszerre. Tehát, ha bármilyen nehéz számítási feladatot végrehajtunk, néha az oldalunk nem reagál, és a felhasználó nem tehet mást, amíg a végrehajtás nem fejeződik be.

Például:

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

A fenti példában, ha a hello metódus előtt hívja az átlagot , akkor az oldala nem reagál, és addig nem tud kattintani a Hellóra, amíg az átlag végrehajtása nem fejeződik be.

Láthatja, hogy amikor az átlagot 10000-as bemenetként hívják meg, ~ 1,82 másodpercbe telt. Ennyi ideig az oldal nem reagál, és nem tudott rákattintani a hello gombra.

Aszinkron programozás

A Javascript lehetővé teszi a fejlesztők számára, hogy aszinkron kódot írjanak . Az aszinkron kód megírásával elkerülheti az ilyen jellegű problémákat az alkalmazásában, mivel lehetővé teszi az alkalmazás felhasználói felületének reagálását, azáltal, hogy „beütemezi” a kód részeit egy kicsit később, az eseményhurokban.

Az aszinkron programozás jó példája XHR request, hogy ebben aszinkron módon eltalálunk egy API-t, és a válaszra várva más kód is végrehajtható. De ez csak a webes API-kkal kapcsolatos bizonyos felhasználási esetekre korlátozódik.

Az aszinkron kód írásának másik módja a használat setTimeoutmódszer. Bizonyos esetekben jó eredményeket érhet el a felhasználói felület feloldásában a hosszabb futó számítások használatával setTimeout. Például komplex számítás kötegelésével külön setTimeouthívásokban.

Például:

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

Ebben a példában láthatja, hogy miután az Átlag kiszámítása gombra kattintott, akkor is rákattinthat a Hello gombra (amely viszont riasztási üzenetet jelenít meg). Ez a programozási mód biztosan nem blokkoló, de túl sok időt vesz igénybe, és a valós alkalmazásokban nem valósítható meg.

Itt ugyanahhoz a 10000 bemenethez ~ 60 másodperc kellett, ami nagyon nem hatékony.

Szóval, hogyan tudjuk hatékonyan megoldani az ilyen jellegű kérdéseket?

A válasz: Webmunkások.

Mik azok a webmunkások?

A Javascript-ben dolgozó webmunkások remek módszerek bizonyos feladatok végrehajtására, amelyek nagyon fáradságosak és időigényesek a fő szálaktól különálló szálakba. A háttérben futnak, és a felhasználói felület beavatkozása nélkül végeznek feladatokat.

A webmunkások nem a JavaScript részei, hanem egy böngészőfunkció, amely a JavaScript segítségével érhető el.

A webmunkásokat egy Worker () függvény hozza létre, amely egy megnevezett JS fájlt futtat.

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

Ha a megadott fájl létezik, akkor az aszinkron módon lesz letöltve, és ha nem, akkor a dolgozó csendesen kudarcot vall, így az alkalmazás továbbra is működik a 404 esetén.

A következő részben többet megtudhatunk az internetes dolgozók létrehozásáról és munkájáról.

A munkásláncnak saját kontextusa van, ezért csak a munkaszálon belüli kiválasztott funkciókat érheti el, például - webaljzatok, indexelt adatbázis.

Van néhány korlátozás az internetes dolgozókkal kapcsolatban -

  1. Nem lehet közvetlenül manipulálni a DOM-ot egy munkavállaló belsejéből.
  2. Nem használhat néhány alapértelmezett módszert és tulajdonságot az ablakobjektumhoz, mivel az ablakobjektum nem érhető el a munkaszálon belül.
  3. A munkavállalói szálon belüli kontextus a felhasználástól függően a DedicatedWorkerGlobalScope vagy a SharedWorkerGlobalScope oldalon érhető el .

A webmunkások jellemzői

Kétféle webmunkás létezik -

  1. Dedikált webmunkás - A dedikált dolgozó csak az általa hívott szkript segítségével érhető el.
  2. Megosztott webmunkás - A megosztott munkatárs több szkript által is elérhető - még akkor is, ha különböző ablakok, iframe-ek vagy akár dolgozók érik el őket.

Beszéljünk többet erről a kétféle webmunkásról -

Webmunkás létrehozása

A létrehozás nagyjából megegyezik a dedikált és a megosztott webmunkásokkal.

Elkötelezett webmunkás

  • Új munkavállaló létrehozása egyszerű, csak hívja a Worker konstruktort, és adja át annak a szkriptnek az útvonalát, amelyet munkavállalóként szeretne végrehajtani.
// create a dedicated web worker const myWorker = new Worker('worker.js');

Megosztott webmunkás:

  • Új megosztott munkatárs létrehozása nagyjából megegyezik az elkötelezett munkavállalóéval, de más konstruktor névvel rendelkezik.
// creating a shared web worker const mySharedWorker = new SharedWorker('worker.js');

Kommunikáció a fő és a dolgozó szál között

A fő szál és a dolgozó szál közötti kommunikáció a postMessage módszer és az onmessage eseménykezelő segítségével történik .

Elkötelezett webmunkás

Elkötelezett webmunkás esetén a kommunikációs rendszer egyszerű. Csak akkor kell használnia a postMessage metódust, amikor üzenetet akar küldeni a munkavállalónak.

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

A webmunkás belsejében pedig az eseménykezelő blokk megírásával válaszolhat, ha az üzenet érkezik:

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

A onmessagekezelő lehetővé teszi bizonyos kód futtatását, amikor üzenet érkezik.

Itt kiszámoljuk a számok átlagát, majd postMessage()újra felhasználjuk az eredmény visszaküldését a fő szálra.

Amint a main.js 6. sorában láthatja, az onmessage eseményt használtuk a munkáspéldányon. Tehát, amikor a munkaszál a postMessage-t használja, a fő szál onmessage-je aktiválódik.

  • Közös webmunkás

    Közös webmunkás esetén a kommunikációs rendszer alig különbözik egymástól. Mivel egy dolgozó több szkript között oszlik meg, a munkavállalói példány port objektumán keresztül kell kommunikálnunk. Ez hallgatólagosan elkötelezett munkavállalók esetén történik. Használnia kell a postMessage metódust, amikor üzenetet akar küldeni a munkavállalónak.

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

Egy webmunkáson belül ( main-shared-worker.js ) ez egy kicsit összetett. Először egy onconnectkezelőt használunk a kód bekapcsolására, amikor a porthoz csatlakozás történik ( 2. sor ).

Ennek portsaz eseményobjektumnak az attribútumát használjuk a port megragadásához és egy változóban történő tárolásához ( 4. sor ).

Ezután hozzáadunk egy messagekezelőt a porton a számítás elvégzéséhez, és az eredményt a fő szálhoz ( 7. és 25. sor ) visszaadjuk így:

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 —

    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.