От автора: это пост № 7 серии, посвященной изучению JavaScript и его компонентов. В процессе определения и описания основных элементов мы используем некоторые правила, которые используем при создании SessionStack , легкого приложения для JavaScript, которое должно быть надежным и высокопроизводительным, чтобы помочь пользователям видеть и воспроизводить дефекты их веб-приложений в режиме реального времени.
На этот раз мы разберем Web Workers JavaScript: предложим обзор, обсудим различные типы Workers, как их компоненты сочетаются вместе, и какие преимущества и ограничения они предлагают в разных сценариях. Наконец, мы предоставим 5 вариантов использования, в которых Web Workers будут правильным выбором.
Вы уже должны быть знакомы с тем, что JavaScript работает в одном потоке, мы мы уже обсуждали это ранее очень подробно. Тем не менее, JavaScript дает разработчикам возможность писать асинхронный код.
Ограничения асинхронного программирования
Прежде мы обсуждали асинхронное программирование, и когда оно должно использоваться.
Асинхронное программирование позволяет пользовательскому интерфейсу приложения реагировать, «планируя» части кода, которые будут выполняться немного позже в цикле событий, таким образом позволяя выполнить в первую очередь визуализацию пользовательского интерфейса.
Хорошим вариантом использования асинхронного программирования является создание запросов AJAX. Поскольку запросы могут занимать много времени, их можно делать асинхронно, и пока клиент ждет ответа, может быть выполнен другой код.
// This is assuming that you're using jQuery jQuery.ajax({ url: 'https://api.example.com/endpoint', success: function(response) { // Code to be executed when a response arrives. } });
Это, однако, создает проблему — запросы обрабатываются WEB API браузером, но как можно сделать асинхронно другой код? Например, если код, который находится внутри успешного обратного вызова сильно нагружает CPU:
var result = performCPUIntensiveCalculation();
Если performCPUIntensiveCalculation не HTTP-запрос, а блокирующий код (например, огромный for цикл), то будет невозможно освободить цикл событий и разблокировать пользовательский интерфейс браузера — он замерзнет и не будет отвечать пользователю.
Это означает, что асинхронные функции решают только небольшую часть ограничений на однопоточность языка JavaScript.
В некоторых случаях вы можете добиться хороших результатов при разблокировании пользовательского интерфейса из более длительных вычислений, используя, например, setTimeout. путем пакетного вычисления сложных вычислений в отдельных setTimeout вызовах, можно поместить их в отдельные «локации» в цикле событий и таким образом выиграть время для визуализации / реагирования пользовательского интерфейса.
Давайте рассмотрим простую функцию, которая вычисляет среднее число массива:
function average(numbers) { var len = numbers.length, sum = 0, i; if (len === 0) { return 0; } for (i = 0; i < len; i++) { sum += numbers; } return sum / len; }
Вот как вы можете переписать вышеприведенный код и «эмулировать» асинхронность:
function averageAsync(numbers, callback) { var len = numbers.length, sum = 0; if (len === 0) { return 0; } function calculateSumAsync(i) { if (i < len) { // Put the next function call on the event loop. setTimeout(function() { sum += numbers; calculateSumAsync(i + 1); }, 0); } else { // The end of the array is reached so we're invoking the callback. callback(sum / len); } } calculateSumAsync(0); }
Здесь будет использоваться setTimeout функция, которая добавит шаг вычисления по контуру события. Между каждым расчетом будет достаточно времени для проведения других расчетов необходимых для разморозки браузера.
Веб-воркеры сэкономят день
HTML5 принес нам много замечательных вещей, вроде этих:
SSE (который мы описали и сравнили с WebSockets в предыдущем посте)
Geolocation
Application cache
Local Storage
Drag and Drop
Web Workers
Web Workers— это потоки в браузере, которые могут использоваться для выполнения кода JavaScript без блокировки цикла событий.
Это действительно потрясающе. Вся парадигма JavaScript основана на идее однопоточной среды, но здесь появляется Web Worker, который устраняет (частично) это ограничение.
Web Worker позволяет разработчикам ставить длинные и интенсивные вычислительные задачи на фоне, не блокируя пользовательский интерфейс, что делает приложение более отзывчивым. Более того, не нужны никакие трюки с setTimeout, чтобы взломать круг событий.
Ниже приведена простая демонстрация, показывающая разницу между сортировкой массива с Web Worker и без него.
демонстрация, показывающая разницу между сортировкой массива с Web Worker и без него.
Обзор веб-воркеров
Web Worker позволяет нам делать такие вещи, как запуск длинных сценариев для обработки вычислительно-интенсивных задач, но без блокировки пользовательского интерфейса. На самом деле все это происходит параллельно. Web Workers действительно многопоточны.
Вы могли бы сказать: «Разве JavaScript не однопоточный?».
И тут должен быть момент вашего «ага!», когда вы понимаете, что JavaScript — это язык, который не определяет модель потоковой передачи. Web Worker не является частью JavaScript, это функция браузера, к которой можно получить доступ через JavaScript. Большинство браузеров исторически были однопоточными (это, конечно, изменилось), и большинство реализаций JavaScript происходят в браузере. Web Workers не реализованы в Node.JS — у них есть понятие «кластер» или «child_process», что немного отличается.
Стоит отметить, что в спецификации упоминаются три типа Web Workers:
Выделенные Workers
Общие Workers
Служебные Workers
Выделенные Workers
Выделенные Web Workers создаются посредством основного процесса и могут общаться только с ним.
Общие Workers
Доступ к общим воркерам может быть достигнут всеми процессами, работающими на одном и том же происхождении (разные вкладки браузера, iframe или другие общие Workers).
Служебные Workers
Служебные Workers — это управляемые событиями Workers, зарегистрированные в отношении источника и пути. Он может управлять веб-страницей / сайтом, с которым он связан, перехватывать и изменять запросы на навигацию и ресурсы, а также очень грамотно кэшировать ресурсы, чтобы дать контроль над поведением приложения в определенных ситуациях (например, когда не доступна сеть).
В этом посте мы сосредоточимся на выделенных воркерах, далее “веб-воркер” или воркер”.
Как работает Web Workers
Web Workers реализованы в виде .js файлов, которые включены через асинхронные HTTP-запросы на странице. Эти запросы полностью скрыты от вас через Web Worker API.
Workers используют потокоподобное сообщение, проходящее для достижения параллельности. Они идеально подходят для поддержания пользовательского интерфейса в актуальном, эффективном и отзывчивом для пользователей виде.
Web Workers работают в изолированном потоке в браузере. Поэтому код, который они выполняют, должен храниться в отдельном файле. Это очень важно запомнить.
Давайте посмотрим, как создается базовый Worker.
var worker = new Worker('task.js');
Если файл «task.js» существует и доступен, браузер будет создавать новый поток, который загружает файл асинхронно. Сразу после завершения загрузки он будет выполнен и появится Worker.
Если предоставленный путь к файлу возвращает 404, Worker потерпит неудачу. Чтобы запустить созданный Worker, нужно вызвать postMessage метод:
worker.postMessage();
Связь с Web Worker-ом
Чтобы установить связь между Web Workers-ом и созданной им страницей, вам необходимо использовать postMessage метод или Broadcast Channel.
Метод postMessage
Новые браузеры поддерживают JSON объект как первый параметр метода, в то время как более старые браузеры поддерживают только string.
Давайте посмотрим пример того, как страница, которая создает Worker, может связываться с ним, передавая объект JSON как более «сложный» пример. Передача строки абсолютно такая же.
Давайте посмотрим на следующую HTML страницу (или ее часть, если быть более точным):
<button onclick="startComputation()">Start computation</button> <script> function startComputation() { worker.postMessage({'cmd': 'average', 'data': [1, 2, 3, 4]}); } var worker = new Worker('doWork.js'); worker.addEventListener('message', function(e) { console.log(e.data); }, false); </script>
Вот как выглядит скрипт:
self.addEventListener('message', function(e) { var data = e.data; switch (data.cmd) { case 'average': var result = calculateAverage(data); // Some function that calculates the average from the numeric array. self.postMessage(result); break; default: self.postMessage('Unknown command'); } }, false);
Когда кнопка нажата, с главной страницы вызывается postMessage. Линия worker.postMessage проводит JSON объект к Workerу, добавляя ключи cmd и data соответствующими значениями. Worker обработает это сообщение через определенный message обработчик.
Когда сообщение приходит, фактические вычисления выполняются в Worker-е, не блокируя цикл событий. Worker проверяет пройденное событие и выполняет его как стандартную функцию JavaScript. Когда это будет сделано, результат вернётся на главную страницу.
В контексте Worker-а, self и this — глобальное пространство.
Существует два способа остановить Worker: вызвать worker.terminate()с главной страницы или вызвать self.close() внутри самого Worker-а.
Broadcast Channel
Broadcast Channel — более обобщённый API для связи. Он позволяет передавать широковещательные сообщения ко всем контекстам, использующим одно и то же происхождение. Все вкладки браузера, iframe или Worker, обслуживаемые одним и тем же источником, могут отправлять и получать сообщения:
// Connection to a broadcast channel var bc = new BroadcastChannel('test_channel'); // Example of sending of a simple message bc.postMessage('This is a test message.'); // Example of a simple event handler that only // logs the message to the console bc.onmessage = function (e) { console.log(e.data); } // Disconnect the channel bc.close()
Вы можете посмотреть, как выглядят каналы вещания, чтобы было более понятно:
Широковещательный канал имеет более ограниченную поддержку браузера:
Размер сообщений
Существует два способа отправки сообщений Web Worker-у:
Копирование сообщения: сообщение сериализуется, копируется, отправляется и затем де-сериализуется на другом конце. Страница и Worker не используют один и тот же экземпляр, поэтому конечный результат состоит в том, что на каждом проходе создается дубликат. Большинство браузеров реализуют эту функцию автоматически JSON, кодируя / декодируя значение с обоих концов. Как и ожидалось, эти операции с данными приводят к значительно накладным расходам на передачу сообщений. Чем больше сообщение, тем больше времени требуется для отправки.
Передача сообщения: это означает, что исходный отправитель больше не может использовать его после отправки. Передача данных происходит практически мгновенно. Ограничение заключается в том, что передавать может только ArrayBuffer.
Возможности, доступные Web Worker
Из-за своего многопоточного характера Web Workers имеют доступ только к подмножеству функций JavaScript. Вот список возможностей:
Объект navigator
Объект location (только для чтения)
XMLHttpRequest
setTimeout()/clearTimeout() а также setInterval()/clearInterval()
Кэш приложений
Импорт внешних скриптов с использованием importScripts()
Создание других веб-Workerов
Ограничения Web Workers
К сожалению, Web Worker не имеeт доступа к некоторым очень важным функциям JavaScript:
DOM (он не является потокобезопасным)
Объект window
Объект document
Объект parent
Это означает, что Web Worker не может манипулировать DOM (и, следовательно, пользовательским интерфейсом). Иногда это может быть сложно, но как только вы узнаете, как правильно использовать Web Worker, вы начнете использовать его в качестве отдельный «вычислительной машины», в то время как в вашем коде страницы будут иметь место все изменения пользовательского интерфейса. Worker сделает для вас тяжелую работу, и как только задания будут выполнены, вы передадите результаты на страницу, которая вносит необходимые изменения в пользовательский интерфейс.
Обработка ошибок
Как и в любом JavaScript-коде, вы захотите обработать ошибки, которые возникают в Web Workers. Если во время выполнения Worker-а произошла ошибка, ErrorEvent запускается. Интерфейс содержит три полезных свойства для выяснения того, что пошло не так:
filename — имя рабочего скрипта, вызвавшего ошибку
lineno — номер строки, в которой произошла ошибка
message — описание ошибки
Например:
function onError(e) { console.log('Line: ' + e.lineno); console.log('In: ' + e.filename); console.log('Message: ' + e.message); } var worker = new Worker('workerWithError.js'); worker.addEventListener('error', onError, false); worker.postMessage(); // Start worker without a message.
self.addEventListener('message', function(e) { postMessage(x * 2); // Intentional error. 'x' is not defined. };
Здесь вы можете видеть, что мы создали Worker и начали просматривать error событие.
Внутри Worker-а (в workerWithError.js) мы создаем преднамеренное исключение, умножая x на 2, пока x не определено в этой области. Исключение распространяется на исходный скрипт и onError вызывается с информацией об ошибке.
Хорошие примеры использования для Web Workers
До сих пор мы перечисляли сильные и слабые стороны Web Workers. Давайте посмотрим, какие самые оптимальные варианты использования для них:
Трассировка лучей: это метод рендеринга для генерации изображения путем отслеживания пути света в виде пикселей. Трассировка лучей использует математические вычисления с интенсивным использованием процессора, чтобы имитировать путь света. Идея состоит в том, чтобы имитировать некоторые эффекты, такие как отражение, преломление, материалы и т. Д. Вся эта вычислительная логика может быть добавлена к Web Workers, чтобы избежать блокировки потока пользовательского интерфейса. Еще лучше — вы можете легко разделить рендеринг изображений между несколькими рабочими (и соответственно между несколькими процессорами). Ниже приведена простая демонстрация трассировки лучей с использованием веб-рабочих - https://nerget.com/rayjs-mt/rayjs.html.
Шифрование: сквозное шифрование становится все более популярным из-за строгой регламентации персональных и конфиденциальных данных. Шифрование может быть чем-то довольно трудоемким, особенно если есть много данных, которые нужно часто зашифровывать (например, перед отправкой на сервер). Это очень хороший скрипт, в котором Web Worker может использоваться, поскольку он не требует никакого доступа к DOM или чему-то интересному — это чистые алгоритмы, выполняющие свою работу. Когда-то в рабочем состоянии он полностью интегрируется с конечным пользователем и не влияет на их опыт.
Предварительная выборка данных: чтобы оптимизировать ваш веб-сайт или веб-приложение и улучшить время загрузки данных, вы можете использовать Web Workers для загрузки и хранения некоторых данных заранее, чтобы вы могли использовать их позже, когда это необходимо. В этом случае Web Workers удивительны, потому что они не будут влиять на пользовательский интерфейс вашего приложения, в отличие от того, когда это делается без Worker-ов.
Прогрессивные веб-приложения: они должны загружаться быстро, даже если сетевое соединение неустойчиво. Это означает, что данные должны храниться локально в браузере. Именно здесь вступает в игру IndexDB или подобные API. В основном, требуется хранение на стороне клиента. Для использования без блокировки потока пользовательского интерфейса работа должна выполняться в Web Worker, в случае с IndexDB существует асинхронный API, который позволяет вам делать это даже без Worker-а, но раньше был синхронный API (он может быть введен снова), который должен использоваться только внутри Worker-а.
Проверка орфографии: базовая проверка орфографии работает следующим образом: программа считывает файл словаря со списком правильно записанных слов. Словарь анализируется как дерево поиска, чтобы сделать фактический текстовый поиск эффективным. Когда слово предоставляется контролеру, программа проверяет, существует ли она в предварительно построенном дереве поиска. Если слово не найдено в дереве, пользователю могут быть предоставлены альтернативные варианты написания, подставляя альтернативные символы и проверяя, является ли это допустимым словом — если это слово, которое пользователь хотел написать. Вся эта обработка может быть легко выгружена Web Workers-ом, чтобы пользователь мог просто вводить слова и предложения без какой-либо блокировки пользовательского интерфейса, в то время как Worker выполняет весь поиск и предоставление предложений.
Производительность и надежность очень важны для нас в SessionStack . Причина, по которой они так важны, заключается в том, что после того, как SessionStack интегрирован в ваше веб-приложение, он начинает записывать все изменения в DOM и взаимодействия пользователя с сетевыми запросами, необработанными исключениями и сообщениями отладки. Все эти данные передаются на наши серверы в режиме реального времени, что позволяет воспроизводить проблемы из ваших веб-приложений в виде видеороликов и просматривать все, что произошло с вашими пользователями. Все это происходит с минимальной задержкой и накладными расходами для вашего приложения.
Вот почему мы выгружаем логику из нашей библиотеки мониторинга и плеера в Web Worker, которые обрабатывают очень сложные для ЦП задачи, такие как хэширование для проверки целостности данных, рендеринга и т. Д.
Веб-технологии постоянно меняются и развиваются, поэтому мы идем на лишнюю милю, чтобы гарантировать, что SessionStack очень легкий и имеет нулевое влияние на приложения наших пользователей.
Существует бесплатный вариант, если вы хотите попробовать SessionStack
Автор: Alexander Zlatkov
Источник: https://blog.sessionstack.com/
Редакция: Команда webformyself.