Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Добавляет статью про Web Workers #5190

Merged
merged 19 commits into from Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
195 changes: 195 additions & 0 deletions js/web-workers/index.md
@@ -0,0 +1,195 @@
---
title: "Web Workers"
description: "Улучшаем производительность приложений при помощи Web Workers и переносим сложные задачи в фоновые потоки, чтобы не замедлять пользовательский интерфейс."
authors:
- marsel
related:
- js/async-in-js
- tools/how-the-browser-creates-pages
- tools/multitasking-and-processes
tags:
- article
---

## Что такое Web worker?

Web worker — это API, которое позволяет выполнять код вне основного потока. Благодаря этому долгие или сложные вычисления, которые выполняются на воркерах, не блокируют пользовательский интерфейс (UI).

Веб воркер создается в основном потоке. При создании воркеру передается URL-адрес скрипта. После загрузки создается отдельный поток, в котором выполнится скрипт воркера.

У скрипта будет свой собственный контекст, отличный от контекста окна `window`. В основном потоке глобальный контекст привязывается к переменной `window`, а в воркере — к переменной `self`. Контекст выполнения веб воркера [WorkerGlobalScope](https://html.spec.whatwg.org/multipage/workers.html#workerglobalscope) отличается от контекста выполнения основного потока. У него нет доступа к объекту документа `document` и `DOM API`.

## Особенности потока выполнения

Поток, в котором выполняется код воркера, изолирован от основного. В Chromium каждому из этих потоков соответствует свой собственный экзепляр [движка JavaScript](https://v8docs.nodesource.com/node-0.8/d5/dda/classv8_1_1_isolate.html). Из-за этого создание нового воркера считается «тяжёлой» операцией. Обычно предполагают, что воркеров будет немного, и они будут долго жить.

Потоки могут общаться между собой через отправку сообщений. Используйте для отправки сообщений функцию [`postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage).

<aside>

📨 При помощи `postMessage()` можете косвенно управлять DOM основного потока. Учитывайте, что механизм `postMessage` — асинхронный. То есть основной поток подписывается на событие `message`, воркер отправиляет сообщение-команду для обновления DOM, а основной поток её обрабатывает и обновляет DOM.

</aside>

### Как создать и запустить?

Всё просто: назовите конструктор `Worker` и передайте туда URL-адрес JavaScript-файла.

```tsx
// window context app.js
const worker = new Worker('worker.js');
```

Воркер использует механизм сообщений для общения с основным потоком. Для отправки сообщения используется метод `postMessage()`.

```tsx
// Основной поток: app.js
const worker = new Worker('worker.js');

// Отправляем сообщение из основного потока в воркер
worker.postMessage({ message: '415тый, я база, ответьте' });
```

В глобальном контексте воркера есть обработчик `onmessage()`. Его можно использовать, чтобы принимать сообщения. Воркер также может отправлять сообщения в основной поток при помощи функции `postMessage()`. Функцию можно вызывать в любом месте воркера.

```tsx
// Вокрер: worker.js
onmessage = function (e) { // Слушаем сообщения из основного потока
if (e.message === "415ый, я база, ответьте") {
// Отправляем сообщение из воркера в основной поток
postMessage("База, это 415ый, как слышно?");
}
};
```

Чтобы получать сообщения в основном потоке, используйте метод-обработчик `onmessage` объекта `Worker`.

```tsx
// window context app.js
const worker = new Worker("worker.js");

worker.postMessage({ message: "415тый, я база, ответьте" });

worker.onmessage = function (e) { // Слушаем сообщения из воркера
console.log(e);
// База, это 415-ый, как слышно?
};
```

Внимательный читатель заметит, что в воркер отправился объект со свойством `message`, а от воркера пришла строка. В функцию `postMessage()` можно передавать значения любого типа, включая объекты. Единственное ограничение — передаваемые данные должны поддерживать [алгоритм структурированного клонирования](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types).

## Что доступно внутри?

Ранее упоминали, что в контексте выполнения воркера недоступны многоие API из объекта `window` основного потока. Что же тогда доступно? Перечислим некоторые функции API, которые часто ипользуются: `fetch()`, `setInterval()`, `setTimeout()`, `requestAnimationFrame()` и `queueMicrotask()`. Для любознательных — [полный список поддерживаемых API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers).

## Типы воркеров

В примере выше рассмотрели первый тип воркеров — `Dedicated Worker`. Он будет доступен только в том потоке, который его создал. Это может быть основной поток или поток другого воркера. Но что, если мы хотим использовать воркер в разных вкладках браузера? Для этого используют другой типа воркера — `Shared Worker`.

### Shared Worker

Shared Workers позволяет создать поток, разделяемый между несколькими вкладками, iframe или окнами в пределах одного и того же происхождения (origin). Это означает, что Shared Worker может быть использован одновременно несколькими частями веб-приложения для обмена данными, синхронизации состояний или выполнения фоновых задач без необходимости повторной загрузки или дублирования в каждой вкладке или окне. Тут стоит отметить, что состояние Shared Worker будет живо, пока о нём кто-то помнит.

Разберёмся, как создать и запустить Shared Worker.

Логика схожа с логикой `Dedicated Worker`, но есть несколько исключений. Во-первых, для создания `SharedWorker` нужно использовать конструктор `SharedWorker`. Во-вторых, `onmessage` и `postMessage` доступны в свойстве воркера `port`:

```tsx
// Первая вкладка: app1.js
const sharedWorker = new SharedWorker("worker.js");

sharedWorker.port.onmessage = (event) => {
console.log("data from worker", event);
};

const sendDataToWorker = () => {
sharedWorker.port.postMessage(1);
};
```

То же самое делаем в другой вкладке:

```tsx
// Первая вкладка: app2.js
const sharedWorker = new SharedWorker("worker.js");

sharedWorker.port.onmessage = (event) => {
console.log("data from worker", event);
};

const sendDataToWorker = () => {
sharedWorker.port.postMessage(2);
};
```

Код SharedWorker выглядит так:

```tsx
// Воркер: worker.js
let sum = 0;

onconnect = (connect) => {
const port = connect.ports[0]; // В ports всегда один элемент

port.onmessage = (event) => {
sum += event;
};

port.postMessage(sum);
};
```

Обработчик события `onconnect` принимает event (мы называем его `connect`). Внутри обработчика используется свойство `ports` – массив в котором всегда будет один элемент. Используя свойство `onmessage` объекта port можно подписаться на сообщения из других потоков. Отправка сообщения также происходит через `port`.

### Вложенность Web Workers

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

### Импорты в Web workers

Начиная с июня 2023 года, практически все браузеры поддерживают импорт ES-модулей в контексте воркеров. Поэтому можно использовать конструкцию `import xxxxx from ‘lib’`. Эта информация пригодится вам при настройке сборки приложения.

### Отправка данных в Web worker

Данные передаваемые в `postMessage()` по-умолчанию копируются, что может быть медленно, особенно при передаче больших или сложных объектов.

### Отправка данных без копирования

Для оптимизации производительности и минимизации затрат на копирование данных можно использовать технику передачи данных через *Transferable objects*. Transferable objects не копируются а перемещаются между контекстами. После оправки Transferable object пропадает из места откуда его отправили. Примерами transferable objects являются `ArrayBuffer` и `MessagePort`.

### Пример использования Transferable objects

Отправка данных в веб воркер:

```jsx
// Создание ArrayBuffer
var buffer = new ArrayBuffer(1024); // 1024 байта

// Отправка ArrayBuffer в воркер
worker.postMessage(buffer, [buffer]);

// теперь buffer не доступен в основном потоке

```

Прием данных в воркере:

```jsx
onmessage = function(e) {
var buffer = e.data; // Получение ArrayBuffer
// Можно начать работу с данными
};

```

В этом примере объект типа `ArrayBuffer` отправляется в воркер через `postMessage`, массив с этим объектом также передается вторым аргументом как transferable object.

Обратите внимание:
- После передачи transferable object, источник теряет доступ к объекту. Это значит, что объект нельзя использовать в источнике после его отправки.
- Не все типы могут быть переданы как transferable object. `ArrayBuffer`, `MessagePort` точно можно перемещать.

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

## Заключение:

Воркеры это мощный инструмент для разработки более отзывчивых и производительных веб-приложений. Они позволяют разгрузить основной поток от тяжелых вычислений улучшают пользовательский опыт. Однако нужно учитывать их ограничения и особенности для эффективного использования в своих проектах.
13 changes: 13 additions & 0 deletions people/marsel/index.md
@@ -0,0 +1,13 @@
---
name: 'Марсель Ахметшин'
url: https://github.com/ByMarsel/
photo: photo.jpg
badges:
- first-contribution-small
---

Привет 😎

Я фронтенд-разработчик в компании Tabby, люблю клепать красивые анимации и ковырять Browser API.

Так же я иногда пишу статейки на [dev.to](https://dev.to/bymarsel), поэтому подписывайся! 💪
Binary file added people/marsel/photo.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.