
postMessage로 데이터를 주고받고, 워커는 terminate로 종료합니다.document에 접근할 수 없어 DOM을 직접 조작하지 못합니다.자바스크립트는 한 번에 하나씩만 처리하는 싱글 스레드(single thread) 방식입니다. 그래서 무거운 로직을 처리하면 다음 로직이 실행되기까지 오래 걸리고, 그동안 브라우저는 멈춘 상태로 남습니다.
이럴 때 사용하는 것이 Worker입니다. Worker는 해당 로직을 다른 스레드에서 처리하게 해 줍니다. 덕분에 브라우저가 멈춰 사용자 경험을 해치는 문제를 해결할 수 있습니다.
다음과 같이 약 3초 정도 오래 걸리는 코드가 있다고 가정해 봅시다.
javascript<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"); }
이 상태에서 slowButton을 클릭하고 바로 simpleButton을 클릭하면, 브라우저는 3초 동안 멈춰 있다가 해당 로직 처리가 끝난 뒤에야 simpleOperation finished를 콘솔에 찍습니다.
이는 매우 나쁜 사용자 경험을 일으키고, 결국 서비스 사용자는 짜증을 느끼고 이탈하게 됩니다.
답은 간단합니다. 워커가 실행할 스크립트 파일을 만들고, 그 안에 워커 스레드에서 처리할 로직을 넣습니다. 그리고 메인에서 실행할 slowOperation 함수 안에서 Worker 생성자에 해당 스크립트 경로를 넘깁니다.
javascript//worker.js for(let i=0; i<3000000000;i+=1) { 1+2; } console.log("slowOperation finished")
javascript//mainScript.js function slowOperation() { const worker = new Worker("worker.js"); }
그러면 slowOperation을 실행할 때 워커 스레드가 해당 로직을 따로 처리하므로 브라우저가 멈추는 문제가 사라집니다.
다만 워커 스크립트에서는 document에 접근할 수 없어 DOM을 직접 조작하지는 못합니다.
그렇다면 메인 스크립트와 통신하려면 어떻게 해야 할까요?
메인 스크립트에서 보낸 데이터를 워커에서 수신하는 방법입니다.
javascriptfunction slowOperation() { const worker = new Worker("worker.js"); // worker 스레드에 10 이라는 데이터를 넘겨준다. worker.postMessage(10) }
javascript// 첫번째 이벤트 수신 방법 (self는 생략이 가능하며, 일반 스크립트 파일에서 window같은 것이라고 보면 된다.) self.addEventListener("message",event=>{ // 10이 출력 console.log(event.data); }) // 두번째 이벤트 수신 방법 (첫번째 혹은 두번째에서 원하는 방식을 사용하면 된다.) onmessage = function(event) { // 10이 출력 console.log(event.data) }
워커 스크립트에서 보낸 데이터를 메인 스크립트에서 수신하는 방법입니다.
javascript// worker.js // self는 생략이 가능하다. self.addEventListener("message",event=>{ self.postMessage(event.data * 10) })
javascriptfunction slowOperation() { const worker = new Worker("worker.js"); // worker 스레드에 10 이라는 데이터를 넘겨준다. worker.postMessage(10); worker.addEventListener("message",event=>{ // 100이 출력 console.log(event.data); }) }
워커를 종료하려면 메인 스크립트에서 선언한 워커 인스턴스의 terminate 메서드를 실행하면 됩니다. 그러면 워커에서 동작하던 스레드가 더 이상 작동하지 않습니다.
javascriptworker.terminate()
프론트엔드에는 참 많은 최적화 방법이 있는 것 같습니다. 사용자 경험을 최우선으로 해야 하는 직업인 만큼, 이런 방법들을 공부하고 사용하는 일은 의미 있고 보람찬 일이라고 생각합니다.
Worker를 알기 전에는 자바스크립트가 싱글 스레드로만 작동하는 언어인 줄 알았는데, 이렇게 멀티 스레드로도 활용할 수 있다는 점은 몰랐습니다.
React에서 무거운 로직을 최적화하는 방법으로 useTransition과 useDeferredValue가 있는데, 아직 실무에서 그만큼 무거운 로직을 처리해 본 경험이 없어 써본 적은 없습니다. 나중에 이 Worker 스레드나 useTransition 같은 훅을 실무에 적용하며 최적화 경험을 쌓아 보고 싶습니다.
// Comments