Уникайте однопотокового циклу подій у браузерах і на сервері. Як використовувати робочі потоки та вебворкери для сучасної багатопоточності в JavaScript, розповідає InfoWorld.
Мова JavaScript має обмеження — однопотокову природу. Щоб досягти справжнього паралелізму, вам потрібно використовувати сучасні багатопотокові підходи, такі як вебворкери та робочі потоки.
Паралелізм проти паралельності
Паралелізм дає змогу наказувати системі (семантиці) виконувати більше ніж одну дію одночасно. Він виконує кілька завдань одночасно (реалізація). Уся паралельна обробка є паралельною, але не все паралельне програмування є паралельним.
У vanilla JavaScript ви можете наказати платформі виконувати кілька речей:
function fetchPerson(id) {
return new Promise((resolve, reject) => {
fetch(`https://swapi.dev/api/people/${id}`)
.then(response => response.json())
.then(data => resolve(data))
.catch(error => reject(error));
});
}
const lukeId = 1;
const leiaId = 5;
console.log("Fetching Star Wars characters...");
// Fetch character data concurrently (non-blocking)
Promise.all([fetchPerson(lukeId), fetchPerson(leiaId)])
.then(data => {
console.log("Characters received:");
console.log(data[0]); // Data for Luke Skywalker (ID: 1)
console.log(data[1]); // Data for Leia Organa (ID: 5)
})
.catch(error => console.error("Error fetching characters:", error));
console.log("Moving on to other things...");
// Fetching Star Wars characters...
// Moving on to other things...
Characters received:
{name: 'Luke Skywalker', height: '172', mass: '77', …}
{name: 'Leia Organa', height: '150', mass: '49', …}
JavaScript планує кожне завдання, яке буде оброблено одним потоком програми.
Це тому, що JavaScript використовує цикл подій. Цикл вибирає матеріали з черги настільки швидко, що часто здається, що це відбувається одночасно, але насправді це не одночасний процес.
Щоб дійсно робити дві речі одночасно, нам потрібно кілька потоків. Потоки — це абстракція базових процесів операційної системи та їхнього доступу до обладнання, включно з багатоядерними процесорами.
Багатопотоковість із вебворкерами
Вебворкери дають змогу створювати потоки у веббраузері. Ви можете просто завантажити робочий сценарій, окремий від основного, і він оброблятиме асинхронні повідомлення. Кожен обробник повідомлень виконується у власному потоці, що забезпечує справжній паралелізм.
У простому прикладі API Star Wars ми хочемо створити потоки, які оброблятимуть або отримуватимуть запити. Використання вебворкерів для цього, очевидно, надмірно, але це робить речі простими.
Ми хочемо створити вебворкер, який прийматиме повідомлення з основного потоку та надсилатиме запити.
Ось як зараз виглядає наш основний скрипт (main.js):
function fetchPersonWithWorker(id) {
return new Promise((resolve, reject) => {
const worker = new Worker('worker.js');
worker.onmessage = function(event) {
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
worker.terminate(); // Clean up the worker after receiving the data
}
worker.postMessage({ url: `https://swapi.dev/api/people/${id}` });
});
}
const lukeId = 1; const leiaId = 5;
console.log("Fetching Star Wars characters with web worker...");
// Fetch character data concurrently (truly parallel)
Promise.all([fetchPersonWithWorker(lukeId), fetchPersonWithWorker(leiaId)])
.then(data => {
console.log("Characters received:");
console.log(data[0]); // Data for Luke Skywalker (ID: 1)
console.log(data[1]); // Data for Leia Organa (ID: 5)
})
.catch(error => console.error("Error fetching characters:", error));
console.log("Moving on to other things...");
Це схоже на перший приклад, але замість використання функції, яка працює локально у Promise.all, ми передаємо функцію fetchPersonWithWorker. Ця остання функція створює об’єкт Worker під назвою worker, який налаштовується за допомогою файлу worker.js.
Після створення робочого об’єкта надаємо для нього подію onmessage. Ми використовуватимемо це для обробки повідомлень, які надходять від працівника.
Після цього викликаємо worker.postMessage() і передаємо простий об’єкт JSON із полем URL-адреси, налаштованим на URL-адресу, яку ми хочемо викликати.
Вебворкер
Ось інша сторона рівняння у worker.js:
// worker.js
onmessage = function(event) {
console.log(“onmessage: “ + event.data); // {"url":"https://swapi.dev/api/people/1"}
const { url } = event.data;
fetch(url)
.then(response => response.json())
.then(data => postMessage(data))
.catch(error => postMessage({ error }));
}
Наш простий обробник onmessage приймає подію та використовує поле URL для виконання тих самих викликів отримання, що й раніше, але цього разу ми використовуємо postMessage() для передачі результатів назад у main.js.
Отже, ви бачите, що ми спілкуємося між двома світами за допомогою повідомлень за допомогою postMessage та onmessage.
Пам’ятайте: обробники onmessage у робочому файлі відбуваються асинхронно у своїх власних потоках. Не використовуйте локальні змінні для зберігання даних, адже вони, швидше за все, будуть стерті.
Читайте також на ProIT: Схвалено стандарт JavaScript ECMAScript 2024.
Підписуйтеся на ProIT у Telegram, щоб не пропустити жодної публікації!