Οι εργαζόμενοι στο Διαδίκτυο σε δράση: γιατί είναι χρήσιμοι και πώς πρέπει να τα χρησιμοποιείτε

Το Javascript είναι απλό νήμα και πολλά σενάρια δεν μπορούν να εκτελεστούν ταυτόχρονα. Επομένως, εάν εκτελέσουμε οποιαδήποτε βαριά εργασία υπολογισμού, τότε μερικές φορές η σελίδα μας αποκρίνεται και ο χρήστης δεν μπορεί να κάνει τίποτα άλλο έως ότου ολοκληρωθεί αυτή η εκτέλεση.

Για παράδειγμα:

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

Στο παραπάνω παράδειγμα, αν σας καλέσει μέσο όρο πριν από την γειά σου μέθοδο, τότε η σελίδα σας θα σταματήσει να ανταποκρίνεται και δεν θα είστε σε θέση να κάνετε κλικ στο Hello μέχρι την εκτέλεση της μέσης παίρνει ολοκληρωθεί.

Μπορείτε να δείτε ότι όταν ο μέσος όρος καλείται με 10000 ως πρώτη είσοδο, χρειάστηκαν ~ 1,82 δευτερόλεπτα. Για το χρονικό διάστημα που η σελίδα αποκρίνεται και δεν μπορείτε να κάνετε κλικ στο κουμπί γεια.

Ασύγχρονος προγραμματισμός

Το Javascript δίνει τη δυνατότητα στους προγραμματιστές να γράψουν κώδικα ασύγχρονου . Γράφοντας ασύγχρονο κώδικα μπορείτε να αποφύγετε τέτοιου είδους προβλήματα στην εφαρμογή σας, καθώς επιτρέπει στο περιβάλλον εργασίας της εφαρμογής σας να ανταποκρίνεται, προγραμματίζοντας τμήματα του κώδικα που θα εκτελεστούν λίγο αργότερα στο βρόχο συμβάντων.

Ένα καλό παράδειγμα του ασύγχρονου προγραμματισμού είναι XHR request, σε αυτό χτυπάμε ένα API ασύγχρονα και ενώ περιμένουμε την απάντηση, μπορεί να εκτελεστεί άλλος κώδικας. Αλλά αυτό περιορίζεται σε ορισμένες περιπτώσεις χρήσης που σχετίζονται κυρίως με διαδικτυακά API.

Ένας άλλος τρόπος σύνταξης κώδικα ασύγχρονου είναι με τη χρήση setTimeoutμέθοδος. Σε ορισμένες περιπτώσεις, μπορείτε να επιτύχετε καλά αποτελέσματα κατά την κατάργηση αποκλεισμού του περιβάλλοντος εργασίας χρήστη από υπολογισμούς που εκτελούνται περισσότερο setTimeout. Για παράδειγμα, ομαδοποιώντας έναν πολύπλοκο υπολογισμό σε ξεχωριστές setTimeoutκλήσεις.

Για παράδειγμα:

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

Σε αυτό το παράδειγμα, μπορείτε να δείτε ότι αφού κάνετε κλικ στο κουμπί Υπολογισμός μέσου όρου , μπορείτε ακόμα να κάνετε κλικ στο κουμπί Γεια σας (το οποίο με τη σειρά του εμφανίζει ένα μήνυμα προειδοποίησης). Αυτός ο τρόπος προγραμματισμού είναι σίγουρα μη αποκλεισμός, αλλά απαιτεί πολύ χρόνο και δεν είναι εφικτός σε πραγματικές εφαρμογές.

Εδώ, για την ίδια είσοδο 10000, χρειάστηκαν ~ 60 δευτερόλεπτα, κάτι που είναι πολύ αναποτελεσματικό.

Λοιπόν, πώς επιλύουμε αυτά τα είδη προβλημάτων αποτελεσματικά;

Η απάντηση είναι Web Workers.

Τι είναι οι εργαζόμενοι στο Διαδίκτυο;

Οι εργαζόμενοι στο Διαδίκτυο στο Javascript είναι ένας πολύ καλός τρόπος για να εκτελέσετε κάποια εργασία που είναι πολύ επίπονη και παίρνει χρόνο σε ένα νήμα ξεχωριστό από το κύριο νήμα. Τρέχουν στο παρασκήνιο και εκτελούν εργασίες χωρίς να παρεμβαίνουν στη διεπαφή χρήστη.

Οι Εργάτες Ιστού δεν αποτελούν μέρος της JavaScript, είναι μια δυνατότητα προγράμματος περιήγησης στην οποία μπορείτε να έχετε πρόσβαση μέσω JavaScript.

Οι εργαζόμενοι στο Web δημιουργούνται από μια συνάρτηση του Worker () που εκτελεί ένα όνομα JS.

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

Εάν το καθορισμένο αρχείο υπάρχει, τότε θα ληφθεί ασύγχρονα και αν όχι τότε ο εργαζόμενος θα αποτύχει σιωπηλά, επομένως η εφαρμογή σας θα εξακολουθήσει να λειτουργεί σε περίπτωση 404.

Στην επόμενη ενότητα θα μάθουμε περισσότερα για τη δημιουργία και τη λειτουργία εργαζομένων στο Διαδίκτυο.

Το νήμα εργαζομένου έχει το δικό του περιβάλλον και επομένως μπορείτε να έχετε πρόσβαση μόνο σε επιλεγμένες λειτουργίες μέσα σε ένα νήμα εργαζομένου όπως - πρίζες web, ευρετήριο DB.

Υπάρχουν ορισμένοι περιορισμοί με τους εργαζόμενους στο Διαδίκτυο -

  1. Δεν μπορείτε να χειριστείτε άμεσα το DOM μέσα από έναν εργαζόμενο.
  2. Δεν μπορείτε να χρησιμοποιήσετε ορισμένες προεπιλεγμένες μεθόδους και ιδιότητες του αντικειμένου παραθύρου, καθώς το αντικείμενο παραθύρου δεν είναι διαθέσιμο σε ένα νήμα εργαζομένου.
  3. Το περιεχόμενο μέσα στο νήμα εργαζομένων μπορεί να προσεγγιστεί μέσω DedicatedWorkerGlobalScope ή SharedWorkerGlobalScope ανάλογα με τη χρήση.

Χαρακτηριστικά των Web Workers

Υπάρχουν δύο τύποι εργαζομένων στο Διαδίκτυο -

  1. Dedicated web worker - Ένας αφοσιωμένος εργαζόμενος είναι προσβάσιμος μόνο από το σενάριο που το ονόμασε.
  2. Κοινόχρηστος διαδικτυακός εργαζόμενος - Ένας κοινόχρηστος εργαζόμενος είναι προσβάσιμος από πολλά σενάρια - ακόμα κι αν έχει πρόσβαση σε διαφορετικά παράθυρα, iframes ή ακόμα και εργαζόμενους.

Ας συζητήσουμε περισσότερα για αυτούς τους δύο τύπους εργαζομένων στο Διαδίκτυο -

Creation of a web worker

Creation is pretty much same for both a Dedicated and Shared web worker.

Dedicated web worker

  • Creating a new worker is simple, just call the Worker constructor and pass the path of the script you want to execute as worker.
// create a dedicated web worker const myWorker = new Worker('worker.js');

Shared web worker :

  • Creating a new shared worker is pretty much the same as that of dedicated worker, but with a different constructor name.
// creating a shared web worker const mySharedWorker = new SharedWorker('worker.js');

Communication between main and worker thread

Communication between main thread and worker thread happens via postMessage method and onmessage event handler.

Dedicated web worker

In case of a dedicated web worker, communication system is simple. You just 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); })();

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 —

    Μπορείτε επίσης να ενσωματώσετε τον κώδικα του εργαζομένου μέσα σε μια ιστοσελίδα (html). Για αυτό πρέπει να προσθέσετε μια ετικέτα σεναρίου χωρίς ένα χαρακτηριστικό src και να εκχωρήσετε έναν μη εκτελέσιμο τύπο MIME σε αυτό, όπως αυτό:

    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 }    

Υπάρχουν πολλές περιπτώσεις χρήσης για χρήση εργαζομένων στο Διαδίκτυο στην εφαρμογή μας. Μόλις συζήτησα ένα μικρό σενάριο. Ελπίζω να σας βοηθήσει να κατανοήσετε την έννοια των εργαζομένων στο Διαδίκτυο.

[Συνδέσεις]

Repo Github: //github.com/bhushangoel/webworker-demo-1 Εργαζόμενος στο Web σε δράση: //bhushangoel.github.io/webworker-demo-1/JS show demo: //bhushangoel.github.io/

Ευχαριστούμε που το διαβάσατε.

Καλή μάθηση :)

Αρχικά δημοσιεύθηκε στο www.thehungrybrain.com.