Javascript является однопоточным языком, независимо от того, какие асинхронные операции вы используете, такие как promises, async, await, settimeout и т.д., все они оказываются в точном стеке вызовов сейчас или позже.
Если вы когда-нибудь работали над корпоративными проектами, вы знаете, что основные проблемы – это оптимизация и производительность. Поэтому мы стараемся сделать пользовательский интерфейс настолько легким, насколько это возможно. Как я уже говорил выше, микрозадачи (async, await…) и макрозадачи (settimeout, setinterval…) не выполняются в отдельных потоках, они просто откладываются и выполняются позже в том же потоке пользовательского интерфейса.
В этой статье мы будем использовать API, доступный в браузерах и NodeJs, для достижения многопоточности в приложениях Javascript.
Итак, давайте начнем с настройки приложения React с помощью Typescript.
npx create-react-app demo-app --template typescript
Что такое веб-рабочие?
Веб-рабочие – это API, предоставляемые браузером/nodeJS, которые будут выносить ваши интенсивные задачи в отдельный поток и выполнять их. Связь между основным потоком и рабочим потоком осуществляется через postMessages(). Web Workers не имеют доступа к некоторым очень важным функциям JavaScript:
- DOM (он не является потокобезопасным).
- Объект окна.
- Объект документа.
- Объект родителя.
Теперь давайте создадим файл в папке src, где мы будем хранить весь интенсивный долго выполняющийся код.
Если вы видите ниже, мы используем postMessage() для связи с главным потоком и // eslint-disable-next-line no-restricted-globals, потому что self нельзя использовать в react без контекстных объектов.
intensiveTasks.ts
const workercode = () => {
// eslint-disable-next-line no-restricted-globals
self.onmessage = function (e) {
const data = e.data
// eslint-disable-next-line no-restricted-globals
self.postMessage(data);
}
setTimeout(() => {
// eslint-disable-next-line no-restricted-globals
self.postMessage("Worker Thread: Hi");
}, 2000);
};
let code = workercode.toString();
code = code.substring(code.indexOf("{") + 1, code.lastIndexOf("}"));
const blob = new Blob([code], { type: "application/javascript" });
const worker_script = URL.createObjectURL(blob);
export default worker_script;
А здесь мы используем тот же postMessage() для связи с рабочим потоком, а myWorker.onmessage используется для получения сообщения обратно от рабочего потока.
App.tsx
import { useEffect, useRef, useState } from 'react';
import './App.css';
import IntensiveTask from './intensiveTask';
function App() {
const [currentMessage, setCurrentMessage] = useState<string>('')
const [allMessages, setAllMessages] = useState<string[]>([])
const initiateWebWorker = () => {
var myWorker = new Worker(IntensiveTask);
// When you want to get messages from Worker Thread
myWorker.onmessage = (message: any) => {
setCurrentMessage(message.data)
};
// When you want to send messages to worker thread
myWorker.postMessage('Main Thread: Hello');
}
useEffect(() => initiateWebWorker(), [])
useEffect(() => setAllMessages([...allMessages, currentMessage]), [currentMessage])
return (
<div className="App">
<div className="App-header">
<b>Web Worker Example</b>
<p>{allMessages.map((data) => <div>{data}</div>)}</p>
</div>
</div>
);
}
export default App;
Вот и все! Теперь вы можете запустить наше приложение react с помощью команды npm start.
GitHub: https://github.com/blessonabraham/webworkers-react
Codesandbox: https://codesandbox.io/s/webworkers-react-8gvljs