WebWorker?
웹 프론트 엔드 프로그래밍이나 모바일 프로그래밍을 하다보면 기존 메인 스레드로 UI 를 렌더링 하기 때문에 백그라운드에서 워커 혹은 다른 스레드를 통해 부가 작업들을 진행한다. MDN 공식문서에도 나와 있듯이 웹에서는 Web Worker 가 그 일을 한다.
알아보기
Worker 는 지정한 javascript 파일을 런타임 환경에서 실행시킬수 있다. MDN 공식문서에 따르면 Worker 를 생성하는 방법은 아래와 같다. `import.meta.url` 은 번들러에 의해 위치가 꼬일수도 있으니 넣으라고 한다.
const myWorker = new Worker(new URL("worker.js", import.meta.url));
Worker 안에서 대부분의 코드가 실행 가능하나 몇가지 목적을 지닌 코드는 실행 불가능한데 해당 코드들은 다음과 같다
- 직접 DOM 을 조작(manipulation) 하려는 시도를 가진 코드
- Window 객체의 기본 메소드나 속성들을 사용하려는 코드
이 두가지 목적을 지닌 코드는 실행 불가능 하니 워커를 자신의 프로젝트에 사용하려고 할때 이 두 가지를 행여나 워커가 실행하려고 하지는 않는지 미리 알아두면 좋을것 같다.
데이터 주고 받는 방법
그렇다면, Worker 와 main thread 간의 통신은 어떻게 이어질까? 예를 들면 worker 에서 데이터를 fetch 해와서 가져왔는데, main thread 에서 이를 받아와 써야 하는 경우에는 어떻게 해야 할까? 이때는 message 시스템의 `postMessage` 와 `onMessage` 메소드를 통해서 데이터를 주고 받을 수 있다. 메소드 명만 보아도 비동기적으로 수행되며 Event 기반임을 대략적으로 예측할 수 있다.
코드 작성
이제 알았으니 코드로 살짝 적어보도록 하자. 예시는 대부분 MDN 에 있는 예시를 가져와서 살짝 입맛에 맞게 변경만 했다.
<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="utf-8" />
<title>웹 워커 예시</title>
</head>
<body>
<div class="controls" tabindex="0">
<form>
<div>
<label for="number1">Multiply number 1: </label>
<input type="text" id="number1" value="0" />
</div>
<div>
<label for="number2">Multiply number 2: </label>
<input type="text" id="number2" value="0" />
</div>
</form>
<p class="result">Result: 0</p>
</div>
<script src="main.js"></script>
</body>
</html>
위 코드를 보면 간단하다. 입력창이 아래와 같이 두개 존재하고, 결과값을 보여주기 위한 result 라는 식별자 클래스를 가진 p 태그 속성 하나가 존재한다. 스크립트 없이 html 만으로는 아무런 동작을 하지 않는다.
일단 단계적으로 조금씩 워커를 이용하는 것으로 마이그레이션 하기 위해서 현재 html 내에서 input(number1, number2) 값에 변화가 있을때 마다 해당 수를 더해서 Result 를 업데이트 시켜주는 자바스크립트 코드를 작성해보자.
const number1 = document.querySelector("#number1");
const number2 = document.querySelector("#number2");
const result = document.querySelector(".result");
[number1, number2].forEach((element) => {
element.onchange = function () {
result.textContent = String(Number(number1.value) + Number(number2.value));
}
})
이렇게 하면 아래와 같이 입력값을 입력했을때 result dom 에 직접 수정을 가해서 입력값을 업데이트 시켜주는 형태이다. Back to the basic ㅋㅋ 정말 오랜만에 JQuery 처럼 코딩하고 있다. 여하튼, 이제 여기서 생각해봐야 할점은 다음과 같다.
- 만약 저 Plus 연산이 Network 혹은 File I/O 작업이라 엄청나게 오래 걸리는 작업이라면 어떻게 될까?
- 만약 main.js 를 돌리는 스레드가 블락킹 되서 다른 작업들을 못하게 된다면?
Worker 로의 연산 이전
그래서 우리는 불필요한 작업들을 Worker 스레드에 조금 분배시킬 수 있도록 작업할 것이다. 우리가 여기서 해야할 것은 Plus 관련된 작업을 워커로 가져간다고 해보자.
onmessage = function (event) {
// main 에서 받는 메세지는 event 의 data 안으로 들어온다
console.log("Received message", event.data);
if (!event.data) {
postMessage("You should send data for calculation!");
}
const {first, second} = event.data;
console.log("Received Successfully", first, second);
const result = Number(first) + Number(second);
postMessage(result);
}
위 worker.js 스크립트는 워커 노드가 해야할 일을 적은 스크립트로 메인 스레드에서는 해당 워커를 생성(spawn) 할때 파일명을 "worker.js" 로 지정해서 spawn 해주면 된다. 여기서 몇 가지 코드만 짚고 넘어가면 좋은데, 첫번째로는 `onmessage` 속성이다. `onmessage` 는 위에서도 설명했듯이, 이벤트가 들어왔을때 어떠한 작업을 하는 일종의 이벤트 핸들러 리스너이다. 따라서 우리가 이벤트를 기다리고 있다가 받으면 하는 일들은 데이터를 더해서 다시 보내주는 일이다.
추측할 수 있겠지만 `postMessage` 는 반대로 데이터를 보내는 역할을 한다. 즉, mainThread(자신을 spawn 한 쪽으로) 로 데이터를 보내는 것이다. 따라서 우리는 연산을 마친 뒤, 워커를 소환한 mainThread 로 결과값을 보내줄 것이다.
const number1 = document.querySelector('#number1');
const number2 = document.querySelector('#number2');
const result = document.querySelector(".result")
if (window.Worker) {
const myWorker = new Worker("./worker.js");
[number1, number2].forEach(input => {
input.onchange = function() {
myWorker.postMessage({
first: number1.value,
second: number2.value,
})
}
})
myWorker.onmessage = function(event) {
result.textContent = event.data;
console.log("Message from worker node : " + event.data);
}
}
이제 mainThread 에서 해야할 일은 input 에 변경값이 생겼을때 worker 에게 메세지를 보내고, worker 가 보내준 결과값이 도달하면 onmessage 를 통해 이벤트를 수신하여 Result 를 업데이트 해주면 된다.
위와 같이 잘 동작하는걸 확인할 수 있다~!
예시코드 링크
https://github.com/tmdgusya/web-worker-example
'JavaScript' 카테고리의 다른 글
Hugo 로 Github 페이지 블로그 만들기 (0) | 2023.08.16 |
---|---|
ES6 이후 함수들 (0) | 2021.09.25 |