JavaScript представляет язык, который выполняется как однопоточный, а это означает, что несколько скриптов не могут выполняться одновременно. Скрипты интерпретируются и выполняются один за другим, строка за строкой.
Кроме того, запуск событий и их обработчиков не происходят параллельно: код, запускающий событие, приостанавливается до тех пор, пока не будут выполнены соответствующие обработчики событий.То же самое относится и к коллбекам - функциям обратного вызова. Например, при отправке на сервер Ajax-запроса скрипт, который отправляет запрос, продолжает выполняться до тех пор, пока сервер не подготовит ответ и не отправит его клиенту. Когда коллбек получит ответ сервера, окружающий код приостанавливается и возобновляет свою работу, лишь когда коллбек завершит обработку ответа сервера.
Web Worker API снимает это ограничение, позволяя обрабатывать задачи параллельно в фоновом режиме. Веб-воркеры выполняются в отдельных потоках. Благодаря веб-воркерам становится возможным выполнять в фоновом режиме параллельно с основным потоком различные ресурсоемкие сценарии, которые в противном случае отрицательно повлияли бы на производительность веб-приложения. Поток веб-воркера может выполнять задачи, не вмешиваясь в пользовательский интерфейс.
Для создания веб-воркера применяется функция-конструктор Worker:
const worker = new Worker("worker.js");
Задачи, которые выполняет веб-воркеров, оформляются в виде отдельных файлов, и в функцию-конструктора в качестве параметра передается путь к скрипту, который будет выполняться веб-воркером.
Создаваемый функций Worker() веб-воркер еще называют выделенным веб-воркером (dedicated web worker).
Следует учитывать, что для загрузки файлов веб-воркеров веб-страница и сами файлы веб-воркеров должны располагаться на веб-сервере. В данном случае в качестве сервера будем использовать Node.js как наиболее простой вариант, но естественно при желании можно использовать любую другую технологию серверного уровня или какой-нибудь веб-сервер.
Рассмотрим простейший пример. Определим для проекта на жестком диске папку, в которой создадим три файла:
index.html: главная страница приложения
worker.js: файл задачи веб-воркера
server.js: файл приложения сервера Node.js
На странице index.html определим следующий код:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <script> const worker = new Worker("worker.js"); </script> </body> </html>
Фактически здесь только создается объект веб-воркера, который будет выполнять код из файла "worker.js".
В файле worker.js определим для демонстрации простейший код:
let result = 1; const intervalID = setInterval(work, 1000); function work() { result = result * 2; console.log("result=", result); if(result >= 32) clearInterval(intervalID); }
Здесь с помощью функции setInterval()
каждую секунду будет выполняться функция work. В функции work мы просто получаем значение переменной result
на 2, сохраняем результат обратно в переменную result и текущий результат выводим на консоль. Когда result достигнет предела - числа 32, то останавливаем
таймер, что приведет к завершению скрипта и соответственно задачи веб-воркера.
Для коректной работы вею-страницы ее надо запускать с веб-сервера. И в данном случае в файле server.js определим код локального веб-сервера Node.js. Определим в нем следующий код:
const http = require("http"); const fs = require("fs"); http.createServer((request, response)=>{ // получаем путь после слеша, слеш - первый символ в пути let filePath = request.url.substring(1); // если пустой путь, отправляем главную страницу index.html if(!filePath) filePath = "index.html"; // в качестве типа ответа устанавливаем html response.setHeader("Content-Type", "text/html; charset=utf-8;"); fs.readFile(filePath, (error, data)=>{ if(error){ // если ошибка response.statusCode = 404; response.end("<h1>Resourse not found!</h1>"); } else{ response.end(data); } }); }).listen(3000, ()=>console.log("Сервер запущен по адресу http://localhost:3000"));
Вкратце пробежимся по коду. Сначала подключаются пакеты с функциональностью, которую мы собираемся использовать:
const http = require("http"); // для обработки входящих запросов const fs = require("fs"); // для чтения файлов с жесткого диска
Для создания сервера применяется функция http.createServer(). В эту функцию передается функция-обработчик, которая вызывается каждый раз, когда к серверу приходит запрос.
Эта функция имеет два параметра: request
(содержит данные запроса) и response
(управляет отправкой ответа).
В функции-обработчике с помощью свойства request.url
мы можем получить путь к ресурсу, к которому пришел запрос. Нам надо обрабатывать запросы к
страницам "index.html" и "home.html" (а в перспективе к любым другим страницам html). Путь всегда начинается со слеша "/". Например, запрос к странице "home.html" будет
представлять путь "/home.html". Соответственно, чтобы получить из запрошенного пути путь к файлам на жестком диске, нам надо убрать начальный слеш:
let filePath = request.url.substring(1);
Однако если запрос обращен к корню сайта, то путь состоит только из одного слеша - "/". Соответственно, если мы удалим этот слеш, то получим пустую строку. Поэтому если запрос идет к корню веб-приложения, то будем считать что запрос идет к главной странице - index.html:
if(!filePath) filePath = "index.html";
И поскольку в нашем случае ответ сервера будет представлять код html, то с помощью метода setHeader()
устанавливаем для
заголовка "Content-Type" значение "text/html":
response.setHeader("Content-Type", "text/html; charset=utf-8;");
То есть ответ сервера будет представлять html.
Далее с помощью функции fs.readFile считываем файл, к которому идет запрос. Первый параметр функции - адрес файла (в данном случае предполагается, что файл находится в одной папке с файлом сервера server.js). Второй параметр - функция, которая вызывается после считывания файла и получет его содержимое через свой второй параметр data. Вполне возможно, что запрошенного файла не окажется, и в этом случае отправляем ошибку 404:
fs.readFile(filePath, (error, data)=>{ if(error){ // если ошибка response.statusCode = 404; response.end("<h1>Resourse not found!</h1>"); }
Если ошибки нет, файл найден и успещно считан, то отправляем параметр data, который содержит данные файла:
else{ response.end(data); }
В конце с помощью функции listen() запускаем веб-сервер на 3000 порту. То есть сервер будет запускаться по адресу http://localhost:3000/
Теперь в консоли перейдем к папке сервера с помощью команды cd и запустим сервер с помощью команды node server.js
C:\app>node server.js Сервер запущен по адресу http://localhost:3000
После запуска сервера мы можем перейти в браузере по адресу http://localhost:3000, нам отобразится страница, в javascript-коде которой будет создан веб-воркер. Этот веб-воркер выполнит задачу, определенную в файле worker.js, а на консоли мы увидим результат этой работы:
В примере выше в коде веб-воркера использовался таймер, создаваемый функцией setInterval()
. Однако не всю функциональность стандартного браузерного JavaScript мы можем
использовать в задачах веб-воркера. Так, веб-воркеры не имеют доступа к DOM и к объекту window. Тем не менее часть возможностей объекта window
(свойств и методов) доступны для веб-воркера (как в случае с функцией setInterval()
). В частности, доступны следующие функции:
atob()
btoa()
clearInterval()
clearTimeout()
queueMicrotask()
setInterval()
setTimeout()
structuredClone()
requestAnimationFrame()
(только для выделенных веб-воркеров)
cancelAnimationFrame()
(только для выделенных веб-воркеров)
Также для веб-воркеров доступны следующие свойства объекта window:
console
location
navigator
indexDB
Кроме того, веб-воркеры могут использовать следующие API:
Barcode Detection API
Broadcast Channel API
Cache API
Channel Messaging API
Console API
Web Crypto API (например, Crypto)
CSS Font Loading API
CustomEvent
Encoding API (например, TextEncoder, TextDecoder)
Fetch API
FileReader
FormData
ImageBitmap
ImageData
IndexedDB
Media Source Extensions API
Network Information API
Notifications API
OffscreenCanvas (и API для работы с контекстом элемента canvas)
Performance API
Server-sent events
ServiceWorkerRegistration
URL API
WebCodecs_API
WebSocket
XMLHttpRequest
С помощью слова self в скрипте веб-воркера (worker.js) мы можем обращаться к объекту веб-воркера:
console.log(self); // получим данные о веб-воркере let result = 1; const intervalID = setInterval(work, 1000); function work() { result = result * 2; console.log("result=", result); if(result>= 32) clearInterval(intervalID); }
Веб-воркер может работать долго вплоть до бесконечности, пока пользователь находится на странице. И может возникнуть вопрос, как завершить выполнение веб-воркера. Для этого интерфейс Worker определяет метод terminate(). Например, изменим код веб-страницы index.html следующим образом:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>METANIT.COM</title> </head> <body> <button id="btn">Stop</button> <script> const worker = new Worker("worker.js"); // по нажатию на кнопку останавливаем работу веб-воркера document.getElementById("btn").addEventListener("click", ()=> { worker.terminate(); console.log("web worker stopped"); }); </script> </body> </html>
Здесь на веб-странице определена кнопка, по нажатию на которую происходит остановка веб-воркера.