Для языка, на котором настроен весь интерфейсный мир, я был удивлен, что Javascript является однопоточным. Верно! Каждая вкладка вашего браузера запускается Javascript, работающим в одном потоке. Это включает в себя весь рендеринг, запросы ajax, прослушиватели событий, setInterval и даже консоль. Процессорное время этого единственного потока делится между всеми ними, чтобы обеспечить нам непрерывную работу, настолько много, что мы даже забываем об этом ограничении.

Javascript отлично работает в одном потоке. Но если он застревает в какой-либо задаче с интенсивным использованием ЦП, его способность визуализировать и управлять DOM будет затруднена. Теперь это очень редко, потому что в большинстве случаев пользовательский интерфейс получает данные от серверной части в форме, готовой к отображению. Но иногда требуется, чтобы ваше веб-приложение обработало некоторые данные (сортировка, вычисление и т. Д.). Если эти данные большие, это может привести к задержке или даже зависанию пользовательского интерфейса, поскольку основной поток занят вычислениями.

Здесь на помощь приходят Web Workers. Web Workers позволяют нам запускать наши собственные скрипты в фоновом режиме в другом потоке. Теперь это не так волшебно, как кажется, поскольку фоновые скрипты не имеют доступа к DOM или каким-либо импортированным компонентам или библиотекам. Но это решит нашу проблему очень хорошо:

  • Главный скрипт, работающий на переднем плане, обрабатывает все манипуляции с DOM.
  • Фоновый рабочий скрипт, обрабатывающий всю тяжелую обработку.

Для запуска некоторого кода в веб-воркерах он должен быть предоставлен как отдельный файл JS. Это связано с тем, что наш основной сценарий и сценарий веб-рабочего должны быть полностью независимыми, чтобы они могли работать в разных потоках. Но тогда как они будут общаться? Метод window.postMessage() безопасно разрешает обмен данными между источниками. Таким образом, два сценария будут взаимодействовать, отправляя (отправляя) и получая (прослушивание) сообщений.

Хватит теории, давайте закодируем!

У меня есть базовое приложение для секундомера, которое, я думаю, идеально подойдет для этой демонстрации. Вы можете найти полный код на Github. Демо-версия находится здесь.

Начнем с создания скрипта, который будет работать в фоновом режиме. Тяжелая вычислительная задача, которую мы собираемся выполнить, - это вложенные циклы for, увеличивающие значение. Мы добавим прослушиватель событий, чтобы прослушивать сообщения из основного скрипта и отправлять результат обратно с помощью postMessage.

Я только что передал здесь одно значение «оценка». Вы также можете передавать массивы, объекты и т. Д.

Примечание: postMessage всегда отправляет копию ваших данных. Таким образом, будет заметное падение производительности, если размер сообщения составляет ›100 МБ.

Но подождите, мы должны предоставить incrementWorker.js как отдельный файл. Это потребует исключения его из существующего рабочего процесса сборки и преобразования его в ES5, а затем ссылки на его URL-адрес для создания нашего Worker… и многих других проблем. Какие-нибудь обходные пути? Да! Это называется Blob.

Большие двоичные объекты позволяют нам создавать временные файлы, на URL-адреса которых можно ссылаться с помощью URL.createObjectURL(). Его конструктор принимает массив значений. Даже если у нас есть только одна строка, которую нужно поместить в большой двоичный объект, мы должны заключить ее в массив.

Давайте создадим класс WebWorker, который сделает все это за нас и вернет нам хорошего воркера.

Мы заключили наш код в (...)(), чтобы сценарий выполнялся сразу после его монтирования. Таким образом, наш скрипт web-worker сразу начинает прослушивать сообщения!

Теперь все, что нам нужно сделать, это создать объект WebWorker с помощью скрипта incrementWorker.js.

Помните, что нам все еще нужно публиковать сообщения и добавлять слушателя для получения сообщений от incrementWorker.js в нашем основном коде . Давайте сделаем это сейчас.

Когда вызывается функция incrementWithWorker, мы отправляем сообщение в сценарий incrementWorker.js с помощью

worker.postMessage(score);

Рабочий обрабатывает и сообщает нам результат, который мы прослушиваем через eventListener, который мы добавили в useEffect при начальном монтировании.

Давайте сейчас ознакомимся с Демо.

Сначала мы проверим, что происходит, когда мы пытаемся выполнить вычисления с использованием основного потока. Включите секундомер и нажмите «Добавить, используя основную ветку».

Сделаны следующие наблюдения:

  • Секундомер останавливается на несколько мгновений.
  • На консоли каждые 500 мс отображаются увеличивающиеся числа. Это на время прекращается.
  • Согласно коду, при подсчете балла loading = true, поэтому кнопки должны быть отключены, а оценка должна быть выделена серым цветом. Но этого не происходит.

Причина в том, что основной поток занят выполнением вложенных циклов for, поэтому он не может обрабатывать задачи, которые он должен выполнять.

Давайте теперь попробуем использовать наш рабочий скрипт. Нажмите «Добавить с помощью ветки WebWorker».

Работает, как и ожидалось. Здесь не замечен ни один из трех упомянутых выше пунктов. Ура !!!

Вы никогда не сможете создать полностью многопоточное веб-приложение. Но с помощью Web Workers можно перенести интенсивную работу ЦП на 1 или даже несколько фоновых сценариев. Это гарантирует, что ваш основной скрипт всегда будет готов ответить пользователю, обеспечивая мгновенный опыт без задержек. И это конечная цель фронтенд-разработки, не так ли?

Спасибо, что дочитали до этого места. Оставьте аплодисменты, если это того стоило.