Animatsiooniline veebiarendus: Jõudlus #3.1 – Dedicated Web Workers

Animatsiooniline veebiarendus: Jõudlus #3.1 – Dedicated Web Workers

Jõudluse kolmandates peatükkides võtame ette sellised toredad asjad nagu Workers.

Kahes esimeses alampeatükis (3.1 ja 3.2) võtame ette nii-öelda veebitöötajad (Web Workers) ja kolmandas Service Workers.

Veebitöötajatest üldisemalt

Põhimõte

Web Worker‘ite põhimõte on see, et saaks jooksutada osa JavaScripti tagaplaanil ehk teistes CPU harudes.

See on jõudluses väga oluline, sest see muudab veebilehe kiiremaks, kuna me ei käivita kõiki skripte ühes harus, vaid erinevates.

Nii ei pea visuaalide eest vastutav lõim (ehk haru) vastutama näiteks krüptimise eest.

Veebitöötajad saavad saata sõnumeid põhikoodi, et neid andmeid saaks edasi kasutada.

Veebitöötajate tehniline pool

Nagu varem mainisin, siis Web Workers jooksutavad ennast teistes CPU harudes, kuid see ei ole kõik.

Nimelt kui lõim on teine, siis ei saa enam kasutada window objekti ega ka DOM’i. Kõik, mida veebitöötaja saab kasutada, peab talle põhiskriptist saatma.

Osad funktsioonid on siiski Web Worker’itel olemas (nagu näiteks setInterval()). Täpsemalt, millised funktsioone saab Web Worker kasutada, võid uurida siit. Lisaks sellele saab Web Worker teha ka XMLHttpRequest’e.

Veebitöötajaid saab luua konstruktoriga (nagu näiteks Worker()) ning sealt edasi saab luua omakorda alamtöötajaid, kuid alamtöötajad peavad olema samal origin‘il.

Suhtlemiseks põhikoodiga on kaks peamist funktsiooni – postMessage(aMessage[, transferList]) ja onmessage(e).

Parameetrid seletan hiljem.

Olemas on kahte sorti veebitöötajaid (kui Service Workers välja arvata) – Dedicated Workers (ehk ühest kohast ligipääsetavad töötajad; teen täna juttu just nendest) ja Shared Workers (ehk igalt poolt kätte saadavad töötajad).

Reaalne kasutus

Tegelikkuses ei ole veebitöötajad midagi keerulist. See on nagu lapsemäng (kui JavaScriptist ka midagi jagad).

Web worker‘eid saab luua läbi konstruktori.

Dedicated Worker‘i konstruktoriks on Worker("worker.js"), mille parameetriks on veebitöötaja JavaScripti faili URL.

Dedicated Worker (edaspidi ka pühendunud veebitöötaja) on töötaja, millele pääseb ligi ainult sellest skriptist, kus ta defineeriti ehk põhikoodist.

Tuleviku mõttes räägin preagu natukene ka Dedicated Worker’ite erinevusest Shared Worker’itega. Nimelt ligipääs Dedicated Worker’ile tähendab seda, et kui loome uue Dedicated Worker’i (new Worker("worker-js)"), siis me loomegi uue Dedicated Worker’i, kuid kui loome uue Shared Worker’i, siis brauser kontrollib, kas oleme juba sellise loonud. Kui jah, siis kasutame olemasolevat, kui ei, siis alles loome uue. Ehk Dedicated Worker’eid luuakse niipalju kui on konstruktoreid, kuid Shared Worker’eid on alati üks, sest need on mitme resursi peale jagatud.

Veebitöötaja kood peab olema teises failis.

Sellest, et töötaja JS peab olema eraldi failis, on võimalik ka hoiduda, kuid lihtsuse mõttes ei räägi ma sellest siin.

Uue töötaja loomine on imelihtne:

var worker = new Worker("worker.js");

worker.js on siis eraldi fail, kus asub veebitöötaja loogika.

Kuna Web Worker‘ite toetus ei ole just kõige parem, siis on alati mõistlik uue töötaja loomisel kasutada tingimuslauset, et kas window objekt tunneb Worker konstruktori ära:

if(window.Worker){
    var worker = new Worker("worker.js")
    //Ülejäänud Worker'i kood
}

Suhtlus veebitöötaja ja põhiskripti vahel käib läbi postMessage(aMessage[, transferList]) ja onmessage(e) funktsioonide läbi.

postMessage() funktsiooni kasutatakse, et saata põhiskriptist andmeid veebitöötajale ja vice versa. Sellega ka tegelikult käivitatakse veebitöötaja.

postMessage() funktsioonil on kaks parameetrit:

  • aMessage – andmed, mida soovime edasi saata. Edasi võib saata ükskõik, mis tüüpi andmeid (sõne, numbreid, objekte jne).
  • transferList – mittekohustuslik; massiiv erinevatest objektidest, et vahetada antud objektide omanikku. Massiivi liikmeteks sobivad ainult Transferable objektid.

Väikene märkus sõnumite kohta. Sõnumil on olemas oma enda kindel definitsioon – “A value that is cloned and not shared is called message” ehk väärtust, mis on kloonitud, aga mitte jagatud, nimetatakse sõnumiks. Järelikult ei jaga me andmeid, vaid saadame nende andmetest koopiad.

Mis nüüd puudutab parameetrit transferList, siis seda kasutatakse, et muuta suurte mahuliste andmete saatmist kiiremaks. Sellisel juhul väärtusi ei kopeerita, vaid reaalselt saadetakse (vahetatakse omaniku). Kuna antud teema läheb praegusest teemast natukene mööda, siis pikemalt ma sellest juttu ei tee, kuid keda huvitab, siis siit ja siit võib leida rohkem infot.

onmessage() aga lubab meil veebitöötaja poolt andmeid kasutada ning ka vastupidi – me saame veebitöötajas põhikoodi poolt saadetud andmeid töödelda.

Kuna onmessage() on osa event handler‘ist, siis tema parameetriks on sõnumi sündmusetöötleja objekt (tihti tähistatud, kas tähega “e” või “event”), millel on olemas omadus data, mis tähistab töötaja/põhiskripti poolt saadetud sõnumit või andmeid.

Selleks, et CPUd mitte ülekoormata, saame ka veebitöötajaid peatada läbi funktsioonide worker.terminate() ja close().

Tegemist on põhimõtteliselt samade funktsioonidega, kuid terminate() tuleb kasutada siis kui peatame veebitöötaja väljaspool veebitöötaja koodi (ehk põhiskriptis) ning close() funktsiooni siis, kui peatame veebitöötaja ise tema enda koodis.

Lisaks sellele terminate() on natukene julmem, kuna terminate() lihtsalt peatab töötaja olenemata sellest, kas see on jõudnud kõik tegevused ära teha.

Kui veebitöötaja on peatatud ei saa seda enam mitte kunagi kasutada. Seega peaks teadma täpselt, millal veebitöötaja töö lõpetatakse.

Vahete vahel võib tekkida olukordi, kus veebitöötaja kasutab mingit teist skripti enda töö tegemiseks.

Selleks, et need oleksid Web Workerile saadaval, on olemas funktsioon importScripts(), mille argumentideks on komadega eraldatud (kui faile on mitu) URId sõnedena, mis viitavad välistele skriptifailidele.

Skriptifailid laetakse täpselt samas järjekorras, nagu on importScripts().

Kui Web Worker ei saa skripti resursist kätte, siis tekib NETWORK_ERROR ja edasist koodi ei käivitata.

Kui peaks juhtuma, et veebitöötajas tekib viga, siis käivitatakse automaatselt onerror, mille data objektis on kolm tähtsamat omadust:

  • message – veateade
  • filename – faili nimi, kus viga tekkis
  • lineno – reanumber

Lisaks sellele on olemas onmessageerror, mis tekib siis, kui andmeid ei saa deserialiseerida (nt madalamas formaadis olevat objekt ei saa muuta arvutile loetavaks).

See ongi pühendatud töötajatega tegelikult kõik. Rohkem keerulist siin pole midagi.

Näited

Põhiskript (main.js):

//Defineerime algmuutujad
var text = "Hello";

//Loome uue pühendunud veebitöötaja
var worker = new Worker("worker.js");

//Käivitame veebitöötaja läbi parameetri text (liigu edasi siit worker.js koodi)
worker.postMessage(text);

//Loome sõnumi event handler'i, et saada sõnumeid/andmeid loodud veebitöötajast
worker.onmessage = function(e){
    console.log(e.data); //"Hello Worker"
}

Pühendunud veebitöötaja (worker.js):

//Väliste teekide/failide impordimine töötajasse
importScripts("foo.js");
importScripts("https://naide.com/bar.js");

//Loome sõnumi event handler'i onmessage, et saada sõnumeid/andmeid põhiskriptist
//Tegemist on tihti ka veebitöötaja protsesside kävitajaks
onmessage = function(e){
    var msg = e.data; //Põhiskritpist saadetud sõnum - "Hello"
    msg += " Worker"; //"Hello Worker"
    postMessage(msg); //Saadame muutuja msg tagasi põhiskripti
    close(); //Peatame töötaja (liigu nüüd põhiskripti worker.onmessage funktsiooni juurde)
}

Võib olla ka märkasid, et kasutasin põhiskriptis pidevalt worker.fun(), kuid veebitöötajas lihtsalt antud funktsiooni. See on nii sellepärast, et kuna veebitöötaja on eraldi CPU lõimus, siis on veebitöötaja ka automaatselt globaalne scope (saame funktsioonidele otse ligi).

Lõpetuseks toon ma välja natukene tõsisema näite veebitöötajate kasutusest.

Tegemist on küll näitega, kuid arvan, et see aitab sul asjdest paremini aru saada.

HTML (index.html):

<!DOCTYPE html>
<html>
    <head>
        <title>Web Workers</title>
        <meta charset="UTF-8" />
    </head>
    <body>
        <h1>Näide veebitöötajate kasulikkusest</h1>
        <h2>Ole selle näite kasutamisel ettevaatlik, kuna "Krüpti ilma veebitöötajata" toiming võib sinu brauseri kinni jooksutada!</h2>
        <h3>Vajadusel muuda "main.js" failis olevat muutajat "loopLength" väiksemaks.</h3>
        <!--Nupp, mis käivitab krüptimise ilma veebitöötajata-->
        <button id="start-button-normal">Krüpti ilma veebitöötajata</button>
        <!--Nupp, mis käivitab krüptimise koos veebitöötajaga-->
        <button id="start-button-worker">Krüpti koos veebitöötajaga</button>
        <!--Animatsioonikast, et näidata veebitöötajate kasulikkust-->
        <div id="animation-box" style="width: 100px; height: 100px; background-color: #009933; margin-top: 20px; border-radius: 20px;"></div>
        <!--CryptoJS-->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js" type="text/javascript"></script>
        <!--VelocityJS-->
        <script src="https://cdn.rawgit.com/julianshapiro/velocity/master/velocity.min.js"; type="text/javascript"></script>
        <!--Põhi JavaScripti fail-->
        <script src="main.js" type="text/javascript"></script>
    </body>
</html>

Põhiskript (main.js):

//Nupud
var startButtonWithoutWorker = document.getElementById("start-button-normal");
var startButtonWorker = document.getElementById("start-button-worker");

//Veebitöötaja
var worker = new Worker("encrypt-worker.js");

//Kui mitu korda me salasõnu krüptime
var loopLength = 5000;

//"startButtonWithoutWorker" nupu vajutamisel käivita siinne, põhiharus olev, encrypt funktsioon
startButtonWithoutWorker.onclick = function(){
    encrypt(loopLength);
};

//"startButtonWorker" nupu vajutamisel käivita veebitöötaja, mis krüpteerib andmeid eralid harus
startButtonWorker.onclick = function(){
    worker.postMessage(loopLength);
};

//Saa andmed veebitöötajast
worker.onmessage = function(e){
    console.group("Krüptimine veebitöötajaga");
    console.log(e.data.msg);
    console.log(e.data.ex);
    console.groupEnd("Krüptimine veebitöötajaga");
};

//Krüpti ühes ja samas harus, milles on brauser
function encrypt(length){
    var encryptedPasswords = [];
    for(var i = 0; i !== length; i++){
        encryptedPasswords[i] = CryptoJS.AES.encrypt((Math.random()*Math.random()).toString(), "Secret Passphrase");
    }
    console.group("Krüptimine ilma veebitöötajata");
    console.log("Valmis krüptimine ilma veebitöötajata");
    console.log("Näide - " + encryptedPasswords[0]);
    console.groupEnd("Krüptimine ilma veebitöötajata");
}

//Animatsioon
Velocity(document.getElementById("animation-box"), {translateZ: 0, translateX: "200px", rotateZ: "180deg"}, {duration: 1000, loop: true});

Veebitöötaja kood (encrypt-worker.js.js):

//Impordi CryptoJS
importScripts("https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js");

//Krüpti salasõnu
function encrypt(length){
    var encryptedPasswords = [];
    for(var i = 0; i !== length; i++){
        encryptedPasswords[i] = CryptoJS.AES.encrypt((Math.random()*Math.random()).toString(), "Secret Passphrase");
    }
    //Saada andmed tagasi põhiskripti
    postMessage({msg: "Valmis krüptimine veebitöötajaga", ex: "Näide - " + encryptedPasswords[0]});
}

onmessage = function(e){
    //Alusta krüptimisega
    encrypt(e.data);
}

Sama lähtekoodi võid leida siit ning selle reaalse näite siit – nagu võid näha, et kui klikkad “Krüpti ilma veebitöötajata”, siis animatsioon hangub täielikult ehk veebileht pole üldse kasutuskõlbulik krüpteerimise ajal. Veebitöötajatega seda probleemi pole.

Võib olla ka märkasid, et põhiharus krüpteerimine on tegelikult kiirem kui web worker’is, kuid see ei tähendaks, et see oleks koheselt parem – nagu just selgitasin, siis veebileht kaotab kasutuskõlbulikuse krüpteerimise ajal. Parem panna kasutaja kauem ootama, kuid näidata, et veebileht on ilusti toimiv, kui kaotada side kasutaja ja veebilehe vahel – see võib tekitada segadust kasutajas. Tänu web workers’itele saame luua laadijaid on, mis on palju informatiivsemad kui hangunud veebileht.

Ja ongi pühendunud veebitöötajatega ühel pool.

Kui sul tekkis küsimusi, siis küsi neid julgelt kommenteerides! 🙂


Senikaua ole tugev ja kohtume juba järgmistes postitustes,

Tähelepanu eest tänades – Oliver Paljak

P.S! Kui oled huvitatud animatsioonilisest veebilehest, siis võta minuga julgelt ühendust AnsiVeebi kodulehel.*
Ma ei pea ennast praegu animatsioonilise veebiarenduse eksperdiks, kuid olen sellele spetsialiseerunud ja saan Sind väga palju aidata animatsioonilise veebilehe loomisel!

Leave a Reply

Your email address will not be published. Required fields are marked *