Web Worker란?
일반적으로 자바스크립트는 한번에 하나씩만 처리하는 싱글스레드 방식이다. 하지만 만약 무거운 로직을 처리하게 된다면 다음 로직이 실행되기까지 오래걸리게 되며, 브라우저는 그동안 block된 상태로 남아있을것이다.
이럴때 사용하면 좋은것이 Worker이다. Worker는 다른 스레드에서 그 로직을 처리할 수 있게 해준다. 때문에 브라우저가 blocking되어 안좋은 사용자경험을 제공해주는것을 해결해준다.
예시
<button id="slow">오래걸리는 task</button>
<button id="simple">간단한 task</button>
const slowButton = document.getElementById("slow")
const simpleButton = document.getElementById("simple")
slowButton.addEventListener("click",slowOperation);
simpleButton.addEventListener("click",simpleOperation);
function slowOperation() {
for(let i=0; i<3000000000;i+=1) {[[
1+2;
}
console.log("slowOperation finished")
}
function simpleOperation() {
console.log("simpleOperation finished");
}
만약 위의 slowOperation 과 같이 한…3초정도 오래걸리는 코드가 있다고 가정해보자.
그러면 slowButton을 클릭하고 바로 simpleButton을 클릭하게 된다면 브라우저는 3초동안 마치 렉걸린것마냥 멈췄다가 저 로직이 다 처리가 되면 그때 simpleOperation finished 이 콘솔에 찍히게 될 것이다.
이것은 매우 안좋은 UX를 야기하게되며, 결국 내 서비스 유저들은 짜증을내고 이탈을 하게 될 것이다
해결책
//worker.js
for(let i=0; i<3000000000;i+=1) {[[
1+2;
}
console.log("slowOperation finished")
//mainScript.js
function slowOperation() {
const worker = new Worker("worker.js");
}
답은 간단하다. worker를 실행시키기 위한 스크립트 파일을 생성하고 그 안에 worker 스레드에서 처리할 로직을 넣는다. 그리고 메인으로 실행 시킬 slowOperation 함수안에 Worker 생성자 변수에 해당 스크립트 path를 넣어준다.
그러면 slowOperation을 실행할때 worker스레드에서 해당 로직을 따로 처리해주게되어 blocking되는 이슈가 사라진다.
하지만 worker 스크립트에서는 document에 접근을 하지 못하기때문에 dom 조작을 직접적으로 하지는 못한다.
만약 main script와 communication을 하기 위해선 어떻게해야할까? 그것은 아래와 같이 하면 된다.
postMessage 활용 ( 메인 스크립트에서 보낸 데이터를 worker에서 수신 )
function slowOperation() {
const worker = new Worker("worker.js");
// worker 스레드에 10 이라는 데이터를 넘겨준다.
worker.postMessage(10)
}
// 첫번째 이벤트 수신 방법 (self는 생략이 가능하며, 일반 스크립트 파일에서 window같은 것이라고 보면 된다.)
self.addEventListener("message",event=>{
// 10이 출력
console.log(event.data);
})
// 두번째 이벤트 수신 방법 (첫번째 혹은 두번째에서 원하는 방식을 사용하면 된다.)
onmessage = function(event) {
// 10이 출력
console.log(event.data)
}
postMessage 활용 ( worker 스크립트에서 보낸 데이터를 메인 스크립트에서 수신 )
// worker.js
// self는 생략이 가능하다.
self.addEventListener("message",event=>{
self.postMessage(event.data * 10)
})
function slowOperation() {
const worker = new Worker("worker.js");
// worker 스레드에 10 이라는 데이터를 넘겨준다.
worker.postMessage(10);
worker.addEventListener("message",event=>{
// 100이 출력
console.log(event.data);
})
}
그렇다면 worker를 종료 시키려면 어떻게 해야할까?
간단하다. 메인스크립트에서 선언한 worker 인스턴스를 사용하여 terminate 메서드를 실행시켜주면 된다.
그러면 worker에서 동작하고 있는 스레드가 작동하지 않게된다.
worker.terminate()
느낀점
백엔드도 마찬가지겠지만, 프론트엔드에서는 참 많은 최적화 시키는 방법들이 있는것 같다. 사용자 경험을 최우선으로 해야하는 직업인만큼 이러한 방법들을 공부하고 사용하는것은 참 의미있고 보람찬것같다.
worker라는것을 알기전에는 자바스크립트는 싱글스레드로만 작동되는 언어인줄 알았는데, 이런식으로 멀티스레드로도 활용할 수 있는 방법이 있는줄은 몰랐다.
react에서는 무거운 로직을 만났을때 최적화 시키는 방법 중 useTransition과 useDeferredvalue가 있는데 사실 아직까지는 실무 개발하면서 저렇게 무거운 로직을 처리해본 경험이 없기때문에 사용해본적이 없다. 나중에 이 worker 스레드나 useTransition 같은 훅을 실무에서 적용해보면서 최적화 시키는 경험을 해보고 싶다.