Worker는 브라우저 안에서 멀티스레딩을 가능하게 하는 Javascript API이다. 기본적으로 Javascript는 싱글 스레드 언어이기에 한 번에 하나의 작업만 수행한다는 사실은 다들 알고 있을 것이다. 이로 인해 복잡한 계산이나 시간이 오래 걸리는 작업을 실행할 때는 사용자의 인터페이스가 멈추거나 응답하지 않는 문제가 발생할 수 있다.
Woker는 이러한 문제를 해결하기 위해 메인 스레드에서 작동하는 Javascript와 별도의 Javascript 코드를 실행할 수 있게 해준다.
다만 메인 스레드에서 작동하는 것은 아니기 때문에 별도의 Scope를 가지고, 이에 따라 DOM에 접근할 수 없는 등 제한적인 메서드를 가진다.
따라서 Worker는 메인 스레드와 통신하기 위해 메세지를 교환하는 방식으로 통신을 진행한다.
그러면 Worker의 3가지 종류에 대해서 알아보자.
가장 기본적인 유형으로 생성된 Web Worker는 각각 별도의 글로벌 스코프를 가진다. 메인 스레드와 통신은 postMessage()함수와 onmessage 이벤트를 통해 할 수 있으며, 생성한 탭 내에서만 사용이 가능하다.
아래는 Web woker로 타이머를 생성하고, 메인 스레드에서 제어하는 예제이다.

<!-- html -->
...
<div id = "timer"/>
<button type="button" id="stopper">stop</button>
...
// main.js
const timeEl = document.querySelector("#timer");
const stopButton = document.querySelector("#stopper");
const worker = new Worker('worker.js'); // 워커 생성
worker.postMessage("start"); // 스크립트가 로드 되는데로 worker에 메세지 전송
worker.onmessage = function(e){ // 워커로부터 받은 메세지에 대한 action
timerEl.innerHTML = e.data;
}
stopButton.addEventListener("click", () => { // 워커에 메세지 보내는 이벤트
worker.postMessage("stop");
});
// worker.js
onmessage = function(e){ // main thread로 부터 받은 메세지
if(e.data == 'start'){
this.intervalId = this.setInterval(()=>{
postMessage(new Date());
},1000)
}
if (e.data === "stop") {
clearInterval(this.intervalId);
}
}
간단하게 살펴봤지만, 만약 연산이 많이 들어가는 피보나치 함수를 생성해서 메인에서만 실행했을 때와, 연산을 워커에 맡겼을 때, 브라우저가 blocking 되는 것을 테스트해 보면 워커가 다른 스레드에서 처리하는 것에 대한 이해가 확실히 될 것 같다.
무거운 연산을 다 web worker에 맡기게 되면 자바스크립트로 충분히 좋은 성능의 연산작업을 할 수 있을 것 같은데 왜 자주 사용하지 않을까?
1. 서버에서 대부분의 데이터를 처리하기 때문에 Web worker를 사용할 만큼 부하가 걸리는 작업이 없다.
2. DOM 조작 및 window 객체의 일부가 메인 스레드에서만 사용이 가능하다는 제약이 따른다.
3. 메인 스레드에서 워커 간 통신에도 비용이 발생한다.
그럼에도 불가피하게 화면 동작에 영향을 미칠 만한 연산이 필요한 경우나, 백그라운드에서 지속적으로 작업을 해야 하는 경우 등 멀티 스레드 환경이 도움 되는 경우가 있다면 사용해 볼 수 있다.
카카오의 경우 암호가 걸린 오피스 파일의 미리보기를 구현하는 데 있어, 텍스트 추출을 클라이언트에서 처리하게 되면서 Web Worker를 사용했다고 한다.
카카오 테크 : 브라우저 Web Worker 다루기 with 오피스 문서 텍스트 추출 및 암호해제
Shared Worker의 가장 큰 특징은 여러 브라우저 컨텍스트(창, 탭, iframe emd) 사이에 데이터를 공유할 수 있다는 점이다. 그런 만큼 통신을 port 객체를 통해 통신하며, post.postMessage() 함수와, port.onmessage 이벤트를 사용한다.
// main.js
const sharedWorker = new SharedWorker("./shared-worker.js"); // shared worker 생성
const port = sharedWorker.port; // 포트
port.start(); // shared worker와 통신 시작
port.postMessage({ action: "getData", id: 123 }); // 워커에게 보내는 메세지
port.onmessage = function (e) { // 워커로 부터 받은 메세지
console.log("메인 스레드 응답:", e.data);
};
// shared-worker.js
onconnect = function (e) {
const port = e.ports[0]; // MessageEvent 객체의 구조와 SharedWorker가 이 객체를 사용하는 매커니즘(항상 하나의 포트만 전달하는 구조) 때문에 0번 인덱스에 할당해놓음
port.start(); // 메인과 통신 시작
port.onmessage = function (e) {
console.log("shared Worker가 받은 메세지:", e.data);
port.postMessage("처리 완료!" + e.data);
};
};
위의 코드를 보면 메인과 shared worker가 통신을 위해 각각 port.start()를 해주고 통신하는 것을 볼 수 있다.
shared worker는 같은 출처(프로토콜, 호스트, 포트)간에 공유되기 때문에 3개의 탭을 띄워놓은 WORKER의 개수만큼 woker Devtool에서 log가 찍히는 것을 볼 수 있다.
Chrome worker devtool


Shared worker는 탭 간에 데이터를 공유할 수 있는 특성을 활용해 실시간 통신을 하는 주식 차트나, 채팅 애플리케이션에서 유용하게 사용할 수 있다. 주식을 예로 들면 1명의 사용자가 수십 개의 탭을 동시에 띄워서 차트를 확인하는데, 이러한 사용자가 1만명 있다고 생각하면 벌써 서버와의 실시간 통신은 수십만 개가 생성되어서 부하가 많이 걸릴 것이다. 이때 shared worker로 하나의 통신으로 여러 개의 탭에 데이터를 전송한다면 각각 서버와 통신하는 것이 아닌 하나의 통신만으로 서버와의 통신을 줄일 수 있다.
Toss 증권 활용 사례
브라우저마다 지원하는 여부가 다른데, iOS Safari및 IE에서는 지원하지 않는 점을 유의하길 바란다.
Service Worker는 앞서 언급한 Web, Shared Worker와 달리 웹 애플리케이션의 백그라운드에서 동작하는 특별한 유형의 Worker이다. 이는 웹페이지의 생명주기와 독립적으로 실행되며, 네트워크 요청을 가로채고 조작할 수 있는 프록시 역할을 한다.

그림과 같이 service worker는 페이지와는 독립적인 생명주기를 가지고 있음을 알 수 있다.
주요 특징은 다음과 같다.
1. 네트워크 프록시: 웹 애플리케이션과 서버 사이의 모든 네트워크 요청을 가로채고 조작할 수 있습니다.
2. 독립적인 생명주기: 웹페이지가 닫혀도 백그라운드에서 계속 실행될 수 있으며, 필요할 때 활성화됩니다.
3. 이벤트 기반 구조: install, activate, fetch, push 등 다양한 이벤트에 반응합니다.
4. HTTPS 필수: 보안상의 이유로 HTTPS 환경에서만 작동합니다(localhost 제외).
5. 캐싱 기능: Cache API를 통해 웹 리소스를 저장하고 관리할 수 있습니다.
// main.js
if ("serviceWorker" in navigator) { // 서비스 워커 생성하기
navigator.serviceWorker
.register("/service-worker.js", { scope: "/" })
.then((registration) => {
console.log("Service Worker 등록 성공:", registration.scope);
})
.catch((error) => {
console.error("Service Worker 등록 실패:", error);
});
}
이렇게 서비스 워커를 생성할 수 있는데. service-worker.js파일 내에서 다양한 이벤트 기반의 행동을 처리하면 msw와 같이 fetch 요청을 가로채, mocking 데이터를 반환하는 처리를 직접 만들어 낼 수 있고, 요청을 가로채 캐싱된 데이터가 있으면 캐싱 데이터를 반환하는 방식으로 클라이언트의 캐싱 및 오프라인에서의 애플리케이션 작동을 구현해 낼 수 있다. 또한 생명주기의 특성을 활용해 PWA처럼 백그라운드에서 Push 알림을 보내는 기능 등을 구현할 수 있다.
지금까지 살펴본 WorkerAPI는 자바스크립트 싱글 스레드 환경에서 멀티 스레딩을 가능하게 하는 강력한 도구이다. 요즘은 대부분의 최신 브라우저들이 지원을 하고 있고, 활용범위도 넓어지고 있는 만큼 잘 사용하면 성능, 효율, 사용자 경험을 향상시킬 수 있는 방향이 많기 때문에 이 부분에 대해 잘 학습해 놓으면 좋을 것 같다.