자바스크립트의 비밀스러운 삶: 클론

발행: (2026년 2월 21일 오후 04:57 GMT+9)
6 분 소요
원문: Dev.to

Source: Dev.to

Web Workers를 사용하여 메인 스레드를 보호하고 UI가 멈추는 것을 방지하는 방법.

Timothy는 Export Report 버튼을 클릭했습니다. 로딩 스피너가 나타났지만 완전히 멈춰 있었습니다. 브라우저 창 전체가 응답하지 않게 되었습니다. 10초 후 UI가 다시 풀리면서 파일이 다운로드되었습니다.

“작동은 해요,” Timothy가 말했습니다, “하지만 데이터 처리 중에 애플리케이션이 완전히 죽어요.”

Margaret는 의자를 끌어당겼다. “Timothy, 당신은 아름다운 주방을 만들었지만 요리사는 한 명뿐이에요. 그에게 만 개의 양파를 썰어 달라고 하면, 동시에 손님을 맞이할 수 없어요.”

The Single Thread

Margaret는 성능 탭을 열고 타임라인을 가득 채운 거대한 고체 노란색 블록을 가리켰다.

“JavaScript는 단일 스레드입니다,”라고 그녀가 설명했다. “우리는 이를 Main Thread라고 부르지만 UI Thread라고 생각하는 것이 좋습니다. 주요 역할은 화면을 그리며, 애니메이션을 실행하고, 클릭을 감지하는 것입니다.”

Timothy는 자신의 코드를 가리키며 말했다. “하지만 나는 asyncawait를 사용했어요! 그게 비차단이라고 생각했거든요.”

async는 기다리기 위한 것입니다,”라고 Margaret가 정정했다. “네트워크 요청을 await하면 요리사가 스프를 가스레인지에 올려놓고 다른 일을 하러 떠나는 것과 같습니다. 하지만 대용량 CSV를 포맷하거나 무거운 수학 연산을 하는 데이터 처리와 같은 작업은 활발한 작업입니다. 요리사가 채를 써는 중이라면 떠날 수 없습니다.”

클론

“화면이 멈추지 않게 데이터를 어떻게 처리하지?”라고 Timothy가 물었다.

“당신은 부주방장을 고용합니다,”라고 Margaret가 말했다. “Web Worker를 만들면 됩니다.”

Margaret는 새로운 파일 worker.js를 추가했습니다:

// worker.js – The Sous‑Chef's Room
self.onmessage = function (event) {
  const rawData = event.data;

  // The chef is chopping the onions in the background
  const processedCSV = heavyDataProcessing(rawData);

  // Send the finished product back to the kitchen
  self.postMessage(processedCSV);
};

Web Worker는 JavaScript 엔진의 실제 클론으로, 원본과 병렬로 실행됩니다. DOM이나 UI 요소에 접근할 수 없지만 fetch()setTimeout() 같은 API는 사용할 수 있습니다.

파견

메인 애플리케이션 파일로 돌아가서, Margaret는 내보내기 버튼을 연결했습니다:

// main.js – The Kitchen (UI Thread)
const exportButton = document.getElementById('export-btn');

exportButton.addEventListener('click', () => {
  // 1. Show the spinning UI
  showLoadingSpinner();

  // 2. Hire the sous‑chef
  const worker = new Worker('worker.js');

  // 3. Listen for the note back under the door
  worker.onmessage = function (event) {
    const processedCSV = event.data;
    downloadFile(processedCSV);
    hideLoadingSpinner();

    // Fire the sous‑chef so he doesn't consume memory
    worker.terminate();
  };

  // 4. Handle emergencies in the kitchen
  worker.onerror = function (error) {
    console.error('Sous‑chef had a breakdown:', error);
    hideLoadingSpinner();
    showErrorMessage();
    worker.terminate();
  };

  // 5. Slide the raw data under the door
  const rawData = getMassiveDataset();
  worker.postMessage(rawData);
});

코드를 다시 실행했을 때, Timothy는 스피너가 부드럽게 회전하는 것을 보고, 다른 UI 요소와 상호작용할 수 있었으며, 10초 후에 파일 다운로드를 받아서 전혀 멈추지 않았습니다.

건축적 분열

“This changes everything,” Timothy remarked, watching the smooth animation.

“It forces you to think differently,” Margaret agreed. “Junior developers put everything on the Main Thread and hope the computers are fast enough to hide it. Senior developers protect the Main Thread at all costs, treating it purely as a presentation layer. If a task takes more than ~50 ms of pure CPU time, they hand it off to a Worker.”

시니어 팁: 전송 가능한 객체

postMessage는 데이터의 복사본을 생성합니다. 대용량 데이터셋의 경우 복사는 비용이 많이 들 수 있습니다. 전송 가능한 객체(예: ArrayBuffer)를 사용하면 메모리 소유권을 복사 없이 워커에게 넘겨 즉시 전달이 이루어지며 오버헤드가 전혀 없습니다.

스피너

작업자가 제자리에 있으면, 스피너는 자유롭게 회전하고 메인 스레드는 반응성을 유지합니다—고객을 맞이하고, 전화를 받고, 장소를 활기차게 유지하면서—부엌 보조는 뒤에서 조용히 양파를 썰고 있습니다.

0 조회
Back to Blog

관련 글

더 보기 »

정의되지 않음 vs 정의되지 않음

Undefined undefined은 JavaScript에서 특수 키워드입니다. 이는 변수가 메모리에 존재하지만 아직 값이 할당되지 않았음을 의미합니다. 생성 단계 동안 o...