Главная » Статьи » Web Worker JavaScript: Руководство для начинающих

Web Worker JavaScript: Руководство для начинающих

Web Worker JavaScript: Руководство для начинающих

От автора: сегодня поговорим о том, для чего нужен Web Worker JavaScript. В 2019 году веб-экосистема эволюционировала до такой степени, что браузер стал средой исполнения для приложений, построенных на JavaScript. Это выражается в скорости, с которой отрасль год за годом предлагает новые фреймворки, парадигмы, загрузчики и сборщики модулей, менеджеры зависимостей, инструменты сборки и менеджеры пакетов.

Когда JavaScript был задуман в первые дни Интернета, направление развития Сети было неясно. Из-за постоянных, быстрых изменений в отрасли и экосистеме, необходимости обратной совместимости браузеров и веб-стандартов развитие JavaScript стало постоянным потоком исправлений, хаков и последствий.

Современные мобильные устройства обычно поставляются с 8+ ядрами ЦП или 12+ ядрами GPU. Настольные и серверные процессоры имеют до 16 ядер или более. В этой среде узкое место — наличие доминирующей однопоточной среды программирования или скриптов.

JavaScript является однопоточным

Это означает, что по замыслу движки JavaScript — изначально браузеры — имеют один основной поток выполнения, или, проще говоря, процесс или функция B не могут быть выполнены до тех пор, пока процесс или функция A не будут завершены. Пользовательский интерфейс веб-страницы не реагирует на любую другую обработку JavaScript, когда он занят выполнением чего-либо — это называется блокировкой DOM.

Это ужасно неэффективно, особенно по сравнению с другими языками. Если мы перейдем на JS Bin и запустим этот код в консоли JavaScript браузера:

//noprotect
i = 0;
while (i < 60000) { console.log("The number is " + i); i++;
}

… весь сайт jsbin.com перестанет отвечать на запросы до тех пор, пока браузер не посчитает и не выведет в консоль до 60 000. Мы не сможем взаимодействовать с чем-либо на странице, потому что браузер занят. Это относительно нетребовательный вычислительный процесс, и современные веб-приложения часто включают в себя гораздо более сложные задачи. Нам нужно уметь вычислять вещи в фоновом режиме, пока пользователь без проблем взаимодействует со страницей.

Web Worker

W3C опубликовал первый проект стандарта Web Worker в 2009 году. С полной спецификацией можно ознакомиться на веб-сайте Рабочей группы по технологиям веб-гипертекстовых приложений — или WHATWG — альтернативной W3C организации по веб-стандартам.

Web Worker — это асинхронная система или протокол, позволяющий веб-страницам выполнять задачи в фоновом режиме независимо от основного потока и пользовательского интерфейса веб-сайта. Это изолированная среда, которая отделена от объекта window, объекта document, прямого доступа в Интернет и лучше всего подходит для длительных или сложных вычислительных задач.

Помимо Web Worker — системы, предназначенной для многопоточности — существуют и другие способы выполнения асинхронной обработки в JavaScript, такие как асинхронные вызовы Ajax и цикл обработки событий. Чтобы продемонстрировать это, мы вернемся к JS Bin и попробуем этот фрагмент кода:

console.log("A");
setTimeout(function(){console.log("B");},2000);
console.log("C");
setTimeout(function(){console.log("D");},0);
console.log("E");
setTimeout(function(){console.log("F");},1000);

Когда мы запускаем это, наша последовательность логов — A, C, E, D, F, B. Браузер сначала планирует операции без времени ожидания по мере их поступления, а затем выполняет функции setTimeout() в порядке указанных соответствующих задержек. Однако эта асинхронность не должна автоматически сопоставляться с многопоточностью. В зависимости от хост-машины это часто может быть просто однопоточный стек вызовов в порядке, который мы объяснили.

Web Worker и многопоточность

Как объясняет справочный веб-сайт Mozilla по JavaScript, Web Worker является «средством для веб-контента запускать скрипты в фоновых потоках».

Мы используем их следующим образом: проверяем наличие конструктора Worker() в браузере и, если он доступен, создаем экземпляр объекта worker с URL-адресом скрипта в качестве аргумента. Этот скрипт будет выполняться в отдельном потоке.
Скрипт должен обслуживаться с того же хоста или домена из соображений безопасности, и это также является причиной того, что Web Worker не будет работать, если мы откроем файл локально по схеме file://.

if (typeof(Worker) !== "undefined") { worker = new Worker("worker.js");
}

Теперь мы определим этот код в файле worker.js:

i = 0;
while (i < 200000) { postMessage("Web Worker Counter: " + i); i++;
}

Разделение потоков

Важным моментом здесь является разделение диапазонов выполнения window и document в главном потоке окна браузера, и диапазона worker.

Чтобы использовать поток worker, эти два диапазона должны быть в состоянии взаимодействовать. Чтобы добиться этого, мы используем в файле worker.js функцию postMessage() — для отправки сообщений в основной поток браузера — и прослушиватель worker.onmessage в основном потоке для прослушивания сообщений worker.

Мы также можем отправлять сообщения из основного потока браузера в поток или функцию worker. Единственное отличие состоит в том, что все происходит наоборот, и мы вызываем worker.postMessage() в основном потоке, а onmessage в потоке worker. Цитата из Mozilla:

Обратите внимание на то, что onmessage и postMessage() должны испускаться из объекта Worker при использовании в главном потоке скрипта, но не тогда, когда используются в Worker. Это связано с тем, что внутри Worker он фактически является глобальной областью действия.

Мы можем использовать метод terminate() таким же образом, чтобы завершить выполнение Worker. Имея все это в виду, рассмотрим пример:

<!DOCTYPE html>
<html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>Web Workers Example</title> <style type="text/css"> body {padding-top:28px;} .output-cont {margin-left:12%; margin-top:28px;} .output-cont h3 {width:200px; height:100%;} .output-cont button {padding:4px 8px; font-size:1.1rem; font-family:sans-serif; } </style>
</head> <body> <div class="output-cont"><button onclick="testWorker()">start worker</button><h3 id="workerOutput"></h3><button onclick="terminateWorker()">terminate worker</button></div>
<br/>
<div class="output-cont"><button onclick="testMainThread()">start blocking thread</button><h3 id="mainThreadOutput"></h3></div>
<br/>
<div class="output-cont"><button onclick="alert('browser responsive!')">test browser responsiveness</button></div> <script> var worker; function testWorker() { if (typeof(Worker) !== "undefined") { if (typeof(worker) == "undefined") { worker = new Worker("worker.js"); } worker.onmessage = function(event) { document.getElementById("workerOutput").innerHTML = event.data; }; } else { document.getElementById("workerOutput").innerHTML = "Web Workers are not supported in your browser"; } } function terminateWorker() { worker.terminate(); worker = undefined; } function testMainThread() { for (var i = 0; i < 200000; i++) { document.getElementById("mainThreadOutput").innerHTML = "Main Thread Counter: " + i; } } </script> </body> </html>

и worker.js:

i = 0;
while (i < 200000) { postMessage("Web Worker Counter: " + i); i++;
}

Это дает нам возможность проверить влияние выполнения основного потока на поведение и производительность страницы по сравнению с влиянием web worker. В этом руководстве мы использовали http-server, чтобы обслуживать файлы локально.

Теперь мы можем видеть, что поток Worker не блокирует интерактивность основного процесса браузера, а циклический просмотр 200 000 чисел не влияет на основной поток. Числа в элементе #workerOutput обновляются при каждой итерации.
Блокирующий поток, или основной поток, когда задействован в цикле, блокирует всю интерактивность (здесь мы установили число итераций 200 000, но это будет еще более очевидно, если мы увеличим его до 2 000 000).

Еще одна вещь, которая указывает нам на заблокированный основной поток, заключается в том, что процесс Worker обновляет страницу при каждой итерации, а цикл в главном потоке (определенный в index.html) обновляет только элемент #mainThreadOutput на последней итерации.

Это связано с тем, что браузер слишком занят подсчетом (циклом for), чтобы иметь возможность перерисовывать DOM, поэтому он делает это только после того, как работа с циклом for завершена (в конце цикла).

Заключение

В этой статье мы представили Web Worker, технологию, которая помогает веб-индустрии идти в ногу со все более и более требовательными веб-приложениями. Это достигается путем предоставления веб-приложениям возможности использовать многопроцессорные и многопоточные устройства, добавляя в JavaScript некоторые многопоточные суперспособности.

Web Worker превращает мобильные и настольные браузерные среды в платформы приложений, предоставляя им среду строгого исполнения. Эта строгость может заставить обеспечивать копирование объектов между несколькими потоками и планировать наши приложения с учетом этих ограничений.

Есть ли у вас какие-либо советы, касающиеся Web Worker? Дайте нам знать об этом в комментариях!

Автор: Tonino Jankov

Источник: https://www.sitepoint.com

Редакция: Команда webformyself.