웹 워커와 서비스 워커
Web Worker로 무거운 계산을 메인 스레드에서 분리하고 Service Worker의 생명주기와 캐시·오프라인 응답을 익힙니다.
우리는 9장에서 Fetch API로 서버 통신, 로컬/세션 스토리지로 클라이언트 데이터 저장, History API로 SPA 라우팅 구현, Canvas API로 웹 그래픽 처리 등 다양한 웹 API와 브라우저 기능들을 학습했습니다.
이제 웹 애플리케이션의 성능과 사용자 경험을 한 단계 더 끌어올릴 수 있는 고급 기술인 웹 워커(Web Workers)와 서비스 워커(Service Workers)에 대해 알아볼 차례입니다.
자바스크립트는 기본적으로 단일 스레드(Single-threaded) 언어입니다.
이 절에서 다루는 페이지 자바스크립트의 이벤트 handler와 DOM 변경은 메인 실행 흐름에서 처리합니다. 그 흐름에 오래 걸리는 계산을 넣으면 다음 이벤트와 화면 갱신을 지연시킬 수 있습니다. 브라우저의 모든 내부 작업이 이 한 스레드에서만 실행된다는 뜻은 아닙니다.
만약 이 메인 스레드에서 복잡하거나 시간이 오래 걸리는 연산(예: 대용량 데이터 처리, 이미지 필터링, 복잡한 애니메이션 계산)을 수행하게 되면, 해당 작업이 끝날 때까지 UI가 멈추거나 반응이 없어지는 블로킹(Blocking) 현상이 발생하여 사용자 경험을 크게 저해합니다.
웹 워커는 메인 스레드 블로킹 문제를 줄이기 위해 자바스크립트 코드를 별도 스레드에서 실행합니다.
이를 통해 계산 비용이 큰 작업을 UI 렌더링 흐름과 분리할 수 있습니다.
서비스 워커는 네트워크 요청 가로채기, 캐싱, 오프라인 응답 같은 기능을 담당하는 브라우저의 백그라운드 실행 단위입니다.
이 절에서는 웹 워커와 서비스 워커의 동작 원리, 기본 사용법, 그리고 활용 시나리오를 정리합니다.
Progressive Web Apps (PWA)에서도 계산 분리와 오프라인 응답에 활용할 수 있습니다. 이 두 기능은 역할이 다르므로, 일반 Worker를 사용하는 페이지가 반드시 Service Worker까지 가져야 하는 것은 아닙니다.
Main thread와 Web Worker 책임 경계 — 상태 추적
본문 소수 요청/배열 결과의 두 메시지와 terminate가 만드는 실행 상태가 다르다. 메인/worker/메시지를 고정해 계산 중 응답 가능성과 종료 뒤 결과 처리 경계를 추적한다. error/messageerror는 별도 경로로 유지한다.
ARCHITECTURE
Main thread와 Web Worker 책임 경계
메인 스레드는 DOM과 UI를 갱신하고 Worker는 별도 실행 환경에서 계산한다. postMessage로 데이터를 복제하거나 Transferable의 소유권을 넘겨 결과를 주고받는다.
숫자 요청과 소수 배열 결과는 원 코드의 두 메시지입니다. 원 main.js는 error를 처리하고 messageerror 리스너는 확장 설계입니다. transfer는 메시지 데이터 소유권 옵션입니다. 오류·terminate는 정상 결과 뒤에 항상 이어지는 단계가 아닌 별도 경로이며, 브라우저 내부의 모든 렌더링 작업을 메인이 독점한다는 뜻은 아닙니다.
- postMessage 요청
- 결과 전송·실행 종료
- error·messageerror
Transferable은 메시지를 보낼 때 선택하는 소유권 옵션이다. 요청 화살표 하나가 한 메시지를 뜻한다. error와 terminate는 정상 결과 뒤의 단계가 아니라 별도 예외·수명 경로다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 작업 요청 | main: UI 작업 / worker: 계산 요청 | main이 소수 계산 입력을 worker에 보내고 DOM을 유지한다 | 메시지 전달은 공유 DOM 접근이 아니다 |
| 계산과 응답 | worker: primes 배열 / main: 응답 대기 | worker가 소수 배열을 계산해 postMessage한다 | worker 결과는 메시지 경계를 통과한다 |
| 반영 또는 종료 | 결과: 현재 요청에 반영 / 종료: worker 중단 | main이 결과를 반영하거나 소유자 종료로 terminate한다 | error/messageerror는 별도 확장 실패 경로로 보존한다 |
Main thread와 Web Worker 책임 경계 — 상태 추적
본문 소수 요청/배열 결과의 두 메시지와 terminate가 만드는 실행 상태가 다르다. 메인/worker/메시지를 고정해 계산 중 응답 가능성과 종료 뒤 결과 처리 경계를 추적한다. error/messageerror는 별도 경로로 유지한다.
ARCHITECTURE
Main thread와 Web Worker 책임 경계
메인 스레드는 DOM과 UI를 갱신하고 Worker는 별도 실행 환경에서 계산한다. postMessage로 데이터를 복제하거나 Transferable의 소유권을 넘겨 결과를 주고받는다.
숫자 요청과 소수 배열 결과는 원 코드의 두 메시지입니다. 원 main.js는 error를 처리하고 messageerror 리스너는 확장 설계입니다. transfer는 메시지 데이터 소유권 옵션입니다. 오류·terminate는 정상 결과 뒤에 항상 이어지는 단계가 아닌 별도 경로이며, 브라우저 내부의 모든 렌더링 작업을 메인이 독점한다는 뜻은 아닙니다.
- postMessage 요청
- 결과 전송·실행 종료
- error·messageerror
Transferable은 메시지를 보낼 때 선택하는 소유권 옵션이다. 요청 화살표 하나가 한 메시지를 뜻한다. error와 terminate는 정상 결과 뒤의 단계가 아니라 별도 예외·수명 경로다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 작업 요청 | main: UI 작업 / worker: 계산 요청 | main이 소수 계산 입력을 worker에 보내고 DOM을 유지한다 | 메시지 전달은 공유 DOM 접근이 아니다 |
| 계산과 응답 | worker: primes 배열 / main: 응답 대기 | worker가 소수 배열을 계산해 postMessage한다 | worker 결과는 메시지 경계를 통과한다 |
| 반영 또는 종료 | 결과: 현재 요청에 반영 / 종료: worker 중단 | main이 결과를 반영하거나 소유자 종료로 terminate한다 | error/messageerror는 별도 확장 실패 경로로 보존한다 |
웹 워커 (Web Workers)
웹 워커는 웹 페이지의 메인 실행 스크립트와 독립된 백그라운드 스레드에서 스크립트를 실행할 수 있도록 해줍니다.
이를 통해 메인 스레드가 무거운 연산으로 인해 블로킹되는 것을 방지하고, 웹 페이지 UI의 반응성을 개선할 수 있습니다.
웹 워커의 특징
- 독립적인 스레드: 웹 워커는 메인 스레드와 별도의 실행 환경을 가집니다.
- DOM 접근 불가: 워커 스레드는 HTML 문서의 DOM에 직접 접근할 수 없습니다. 이는 워커가 UI 업데이트를 직접 수행할 수 없다는 의미입니다.
- 통신 방식: 메인 스레드와 워커 스레드는
postMessage()메서드를 통한 메시지 기반 통신(onmessage이벤트 리스너)으로 데이터를 주고받습니다. 일반 데이터는 구조화 복제되고, Transferable은 소유권을 이전할 수 있습니다. - 제한된 API: 워커는
alert(),confirm()과 같은 브라우저 UI 관련 API에 접근할 수 없습니다. 대신XMLHttpRequest,fetch,IndexedDB,setTimeout,setInterval등은 사용할 수 있습니다. - 동일 출처 정책: 워커 스크립트는 메인 스크립트와 동일한 출처(origin)에 있어야 합니다.
웹 워커 사용법
worker.js)메인 스크립트와 독립적으로 실행될 자바스크립트 파일을 생성합니다.
// worker.js
// 이 스크립트는 웹 워커 스레드에서 실행됩니다.
// 메인 스크립트로부터 메시지를 받으면 실행되는 이벤트 핸들러
self.onmessage = function(event) {
const number = event.data; // 메인 스크립트로부터 전달받은 데이터
console.log(`워커: ${number}까지의 소수 계산을 시작합니다.`);
// 복잡하고 시간이 오래 걸리는 연산 (예시: 소수 계산)
let primes = [];
for (let i = 2; i <= number; i++) {
let isPrime = true;
for (let j = 2; j * j <= i; j++) {
if (i % j === 0) {
isPrime = false;
break;
}
}
if (isPrime) {
primes.push(i);
}
}
console.log(`워커: ${number}까지의 소수 계산 완료.`);
// 계산 결과를 다시 메인 스크립트로 전송
self.postMessage(primes);
};main.js 또는 HTML <script>)메인 페이지 스크립트에서 Worker 객체를 생성하고, postMessage()로 데이터를 주고받습니다.
// main.js (또는 HTML <script>)
const startButton = document.getElementById('startButton');
const resultDiv = document.getElementById('result');
const statusDiv = document.getElementById('status');
let myWorker;
startButton.addEventListener('click', () => {
statusDiv.textContent = "워커 시작 중... 잠시 기다려 주세요.";
// 이전에 워커가 있다면 종료
if (myWorker) {
myWorker.terminate();
myWorker = null;
}
// 새 워커 생성 (워커 스크립트 파일 경로 지정)
myWorker = new Worker('worker.js');
// 워커로부터 메시지를 받을 때 실행될 함수
myWorker.onmessage = function(event) {
const primes = event.data; // 워커로부터 전달받은 소수 배열
resultDiv.textContent = `계산된 소수 개수: ${primes.length}`;
statusDiv.textContent = "계산 완료! (메인 스레드는 블로킹되지 않았습니다.)";
myWorker.terminate(); // 작업이 끝나면 워커 종료
myWorker = null;
};
// 워커에서 에러 발생 시
myWorker.onerror = function(error) {
statusDiv.textContent = `워커 오류: ${error.message}`;
console.error("워커 오류:", error);
};
// 워커에게 계산을 시작하도록 메시지 전송
const numberToCalculate = 500000; // 큰 숫자
myWorker.postMessage(numberToCalculate);
statusDiv.textContent = `워커에게 ${numberToCalculate}까지의 소수 계산 명령을 보냈습니다. (UI는 반응 중)`;
});
// HTML에 버튼과 결과 표시 영역 추가
/*
<button id="startButton">복잡한 계산 시작 (워커 사용)</button>
<div id="status"></div>
<div id="result"></div>
*/이 예시에서 복잡한 소수 계산은 백그라운드 워커 스레드에서 실행되므로, 메인 스레드가 이 계산을 직접 수행하지 않습니다. 다만 메시지 복제·결과 처리와 CPU 경합 비용은 여전히 고려해야 합니다.
계산 중에도 다른 UI 요소들을 조작할 수 있습니다.
워커 종료: worker.terminate()
워커 인스턴스의 terminate() 메서드를 호출하여 워커 스레드를 즉시 종료할 수 있습니다.
이는 더 이상 워커가 필요하지 않을 때 리소스 누수를 방지하는 데 중요합니다.
웹 워커는 계산을 분리해 주지만, 메인 스레드와 데이터를 주고받는 비용도 함께 생깁니다.
앞의 Main thread와 Web Worker 책임 경계 다이어그램은 postMessage, 구조화 복제, Transferable 객체, 작업 종료 기준을 함께 보여줍니다.
서비스 워커 (Service Workers)
서비스 워커는 웹 워커의 한 종류로, 웹 페이지와 네트워크 사이에서 프록시(Proxy) 역할을 하는 자바스크립트 파일입니다.
웹 페이지와 독립된 백그라운드에서 실행되며, 주로 다음 기능을 제공합니다.
- 오프라인 경험: 네트워크 연결 없이도 웹 애플리케이션을 동작하게 합니다.
- 캐싱: 네트워크 요청을 가로채서 캐시에서 리소스를 반환하여 웹 페이지 로딩 속도를 향상시킵니다.
- 푸시 알림: 페이지가 열려 있지 않아도 권한·브라우저·운영체제 지원에 따라 푸시를 처리할 수 있습니다. 오프라인 전달은 연결 복구와 메시지 만료 조건에 영향을 받습니다.
- 백그라운드 동기화: 백그라운드에서 서버와 데이터를 동기화합니다.
서비스 워커는 Progressive Web Apps (PWA)의 핵심 기술이며, 웹 사이트를 모바일 앱처럼 동작하게 만드는 데 필수적입니다.
서비스 워커의 생명주기
Service Worker 설치·활성·제어 수명 — 상태 추적
A/B worker의 installing/waiting/activating/activated와 client controller는 별개다. 고정 registration·버전·client에서 takeover 전후를 보여 주되 idle 실행과 redundant를 구분한다.
STATE · REGISTRATION & VERSION TAKEOVER
Service Worker 설치·활성·제어 수명
설치·대기·활성 상태와 실제 client 제어를 구분한다. 설치 실패·교체에 따른 redundant와 일시적인 실행 종료도 다르다.
실선은 버전 설치·활성 상태, 점선은 별도 client 제어 관계입니다. 설치 성공 뒤에도 A의 client가 남으면 B는 waiting일 수 있습니다. claim은 activate 처리 중에도 요청할 수 있고, 실행이 쉬는 것은 redundant 상태가 아닙니다.
installed state와 registration의 waiting slot, activated state와 실제 client control을 구분해야 업데이트 적용 시점을 판단한다. 이벤트가 없을 때 실행이 종료되는 것은 redundant 상태가 아니며 필요할 때 다시 실행된다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 기존 controller | A: 현재 controller / B: installing | client가 A를 사용하고 새 B를 설치한다 | 등록의 worker state와 client 제어를 분리한다 |
| B 대기 | A: client 제어 / B: waiting | 설치 후 B는 waiting이고 client는 A를 유지할 수 있다 | idle인 worker를 redundant로 해석하지 않는다 |
| 새 제어 | B: activated / client: 정책에 따라 B | 조건에 따라 B activate 및 client takeover가 일어난다 | 활성 worker 존재가 모든 client 즉시 제어와 같은 뜻은 아니다 |
Service Worker 설치·활성·제어 수명 — 상태 추적
A/B worker의 installing/waiting/activating/activated와 client controller는 별개다. 고정 registration·버전·client에서 takeover 전후를 보여 주되 idle 실행과 redundant를 구분한다.
STATE · REGISTRATION & VERSION TAKEOVER
Service Worker 설치·활성·제어 수명
설치·대기·활성 상태와 실제 client 제어를 구분한다. 설치 실패·교체에 따른 redundant와 일시적인 실행 종료도 다르다.
실선은 버전 설치·활성 상태, 점선은 별도 client 제어 관계입니다. 설치 성공 뒤에도 A의 client가 남으면 B는 waiting일 수 있습니다. claim은 activate 처리 중에도 요청할 수 있고, 실행이 쉬는 것은 redundant 상태가 아닙니다.
installed state와 registration의 waiting slot, activated state와 실제 client control을 구분해야 업데이트 적용 시점을 판단한다. 이벤트가 없을 때 실행이 종료되는 것은 redundant 상태가 아니며 필요할 때 다시 실행된다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 기존 controller | A: 현재 controller / B: installing | client가 A를 사용하고 새 B를 설치한다 | 등록의 worker state와 client 제어를 분리한다 |
| B 대기 | A: client 제어 / B: waiting | 설치 후 B는 waiting이고 client는 A를 유지할 수 있다 | idle인 worker를 redundant로 해석하지 않는다 |
| 새 제어 | B: activated / client: 정책에 따라 B | 조건에 따라 B activate 및 client takeover가 일어난다 | 활성 worker 존재가 모든 client 즉시 제어와 같은 뜻은 아니다 |
서비스 워커는 메인 스크립트와는 다른 복잡한 생명주기를 가집니다.
등록 (Registration): 웹 페이지에서 서비스 워커 파일을 등록합니다.
설치 (Installation): 서비스 워커 파일이 다운로드되고, install 이벤트가 발생합니다.
여기서 보통 필요한 애셋(asset)들을 캐시에 저장합니다.
활성화 (Activation): 새 버전은 일반적으로 기존 client가 해제될 때까지 대기한 뒤 activate 이벤트를 처리합니다. 활성 상태와 현재 페이지의 제어 여부는 별개입니다.
제어 (Controlling): 활성 워커가 범위 내 client의 controller가 되면 요청을 처리합니다. 새 탐색이나 clients.claim()으로 기존 페이지의 제어를 시작할 수 있습니다.
종료 (Termination): 사용되지 않을 때는 메모리를 절약하기 위해 종료될 수 있으며, 필요할 때 다시 시작됩니다.
서비스 워커의 fetch 이벤트에서는 method, origin, destination을 확인한 뒤 캐시와 네트워크 중 어느 경로를 사용할지 정책으로 결정합니다.
서비스 워커 사용법 (기초)
sw.js)// sw.js
// 이 스크립트는 서비스 워커 스레드에서 실행됩니다.
const CACHE_NAME = 'my-pwa-cache-v1'; // 캐시 이름 정의
const urlsToCache = [ // 캐시할 파일 목록
'/',
'/index.html',
'/styles.css',
'/main.js',
'/logo.png' // 예시 이미지
];
// install 이벤트: 서비스 워커 설치 시점에 발생
self.addEventListener('install', (event) => {
console.log('[Service Worker] 설치 중...', event);
// 캐싱 작업 수행 (비동기)
event.waitUntil(
caches.open(CACHE_NAME) // 캐시 저장소 열기
.then((cache) => {
console.log('[Service Worker] 모든 리소스를 캐시합니다.');
return cache.addAll(urlsToCache); // 파일들을 캐시에 추가
})
);
});
// activate 이벤트: 서비스 워커 활성화 시점에 발생 (이전 워커 제거 등)
self.addEventListener('activate', (event) => {
console.log('[Service Worker] 활성화 중...', event);
event.waitUntil(
// 이전 버전의 캐시를 삭제하여 업데이트 처리
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
console.log('[Service Worker] 오래된 캐시 삭제:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
// 서비스 워커가 페이지를 즉시 제어하도록 설정
event.waitUntil(self.clients.claim());
});
// fetch 이벤트: 네트워크 요청을 가로챔 (가장 중요!)
self.addEventListener('fetch', (event) => {
console.log('[Service Worker] Fetch 요청 가로챔:', event.request.url);
event.respondWith(
caches.match(event.request) // 요청이 캐시에 있는지 확인
.then((response) => {
// 캐시에 응답이 있으면 캐시된 응답 반환
if (response) {
console.log('[Service Worker] 캐시에서 응답 반환:', event.request.url);
return response;
}
// 캐시에 없으면 네트워크 요청
console.log('[Service Worker] 네트워크 요청:', event.request.url);
return fetch(event.request);
})
);
});main.js 또는 HTML <script>)메인 페이지 스크립트에서 서비스 워커를 등록합니다.
일반적으로 DOMContentLoaded 이벤트 이후에 등록합니다.
// main.js (또는 HTML <script>)
if ('serviceWorker' in navigator) { // 브라우저가 서비스 워커를 지원하는지 확인
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js') // 서비스 워커 파일 경로 지정
.then((registration) => {
console.log('Service Worker 등록 성공:', registration.scope);
})
.catch((error) => {
console.error('Service Worker 등록 실패:', error);
});
});
} else {
console.warn('이 브라우저는 Service Worker를 지원하지 않습니다.');
}이 예제는 나열한 캐시 파일이 모두 존재하는 전용 실습 출처를 전제로 합니다. 다른 앱과 출처를 공유한다면 캐시 삭제를 자신의 이름 접두사로 제한해야 합니다. 캐시되지 않은 요청은 오프라인에서 실패할 수 있습니다.
이 코드를 배포하면, 사용자가 처음 웹사이트에 접속할 때 서비스 워커가 설치되고, 이후에는 네트워크 요청을 서비스 워커가 가로채 캐시에서 응답을 제공하거나 네트워크 요청을 수행할 수 있습니다.
이를 통해 웹 페이지 로딩 속도를 비약적으로 향상시키고, 심지어 네트워크 연결이 없어도 일부 기능을 사용할 수 있는 오프라인 경험을 제공할 수 있습니다.
서비스 워커의 활용 분야
- 오프라인 웹 애플리케이션: PWA의 핵심으로, 네트워크 연결 유무에 상관없이 동작하는 앱 구현.
- 빠른 로딩: 캐싱 전략을 통해 반복 방문 시 페이지 로딩 속도 극대화.
- 푸시 알림: 사용자에게 서버에서 보낸 푸시 메시지 전달.
- 백그라운드 데이터 동기화: 네트워크가 재연결될 때 자동으로 서버와 데이터 동기화.
- 리소스 미리 가져오기(Pre-caching): 사용자가 방문할 가능성이 있는 페이지의 리소스를 미리 캐싱.
웹 워커와 서비스 워커는 이름이 비슷하지만 적용 기준이 다릅니다.
CPU 계산은 Web Worker, 네트워크 중간자·캐시는 Service Worker, DOM 상호작용은 main thread 책임으로 분리합니다.
워커 기반 성능 개선 정리
핵심은 메인 스레드 반응성을 유지하면서 백그라운드 작업을 어떤 기준으로 분리할지 판단하는 것입니다.
이번 절에서는 웹 애플리케이션의 성능과 사용자 경험을 개선하는 두 가지 백그라운드 스크립트 기술인 웹 워커(Web Workers)와 서비스 워커(Service Workers)를 살펴봤습니다.
웹 워커는 복잡한 연산을 메인 스레드 밖에서 처리해 UI 반응성을 유지하도록 돕습니다.
postMessage() 기반 통신과 DOM 접근 불가라는 제약을 함께 기억해야 합니다.
이어서, 서비스 워커가 웹 워커의 개념을 확장하여 웹 페이지와 네트워크 사이에서 프록시 역할을 하며, 오프라인 지원, 캐싱, 푸시 알림 등 PWA의 핵심 기능을 구현하는 데 필수적임을 배웠습니다.
서비스 워커의 생명주기(등록, 설치, 활성화, 제어)와 install, activate, fetch 이벤트를 통한 캐싱 및 네트워크 요청 가로채기 방식의 기초를 알아보았습니다.
웹 워커와 서비스 워커는 PWA와 고성능 웹 애플리케이션에서 자주 쓰입니다.
서비스 워커는 실제 배포 환경에서 HTTPS를 요구하고 디버깅이 복잡할 수 있으므로, Chrome 개발자 도구의 Application 탭에서 상태와 캐시를 확인해야 합니다.
이로써 9장 웹 API와 브라우저 기능의 주요 내용을 마무리합니다.
최신 자바스크립트 기능과 브라우저 API를 함께 이해하면, UI 반응성, 저장, 통신, 백그라운드 처리까지 한 흐름으로 점검할 수 있습니다.
웹 워커와 서비스 워커는 모두 메인 스레드 밖에서 동작하지만, 해결하는 문제가 계산 분리와 네트워크 중간자라는 점에서 다릅니다.
이 절에서는 웹 워커와 서비스 워커의 역할, 사용법, 적용 전 확인할 제약을 정리했습니다.
웹 워커와 서비스 워커를 작업 위치, 생명주기, 캐시 책임 기준으로 정리합니다. 따라하기: 작은 입력으로 Worker 결과부터 확인하기
원래 main.js의 계산을 시작하기 전에 index.html에 <button id="startButton">계산 시작</button><p id="status">대기</p><p id="result"></p><script src="main.js"></script>를 준비합니다. worker.js와 main.js는 주석의 파일 이름대로 나누어 같은 학습 사이트에서 제공합니다. 처음에는 원래 큰 입력을 그대로 기다리기보다 별도 사본에서 numberToCalculate를 10으로 바꾸어 결과 개수 4를 확인하세요. 파일을 직접 여는 file URL 대신 Worker를 제공할 수 있는 학습용 HTTP 환경을 사용합니다.
2부터 10까지 나누어 떨어지는 작은 약수가 없는 수는 2, 3, 5, 7입니다. Worker는 숫자 10을 메시지로 받아 계산한 뒤 [2, 3, 5, 7]이라는 배열을 보냅니다. 메인 스크립트는 배열의 length로 개수를 표시하고 Worker를 종료합니다. Worker에서 document를 사용하는 방식으로 UI 갱신을 옮기는 것은 이 실행 환경의 경계에 맞지 않습니다. 처음에는 버튼을 한 번만 눌러 이 순서를 확인하세요. 원래 버튼은 비활성화되지 않으며, 다시 누르면 이전 Worker를 terminate한 다음 새 Worker를 만듭니다.
독립 연습
같은 소수 알고리즘에서 입력을 30으로 바꾸었습니다. 반환할 배열과 메인 쪽 표시 개수·처리 순서를 적으세요. 또 입력 데이터를 ArrayBuffer의 transfer list로 넘겼다면, 보내고 난 뒤 원래 메인 쪽 buffer를 이전처럼 읽어도 되는지 판단하세요. 이 두 질문은 별도 실험입니다.
계산 결과와 데이터를 보내는 방식을 나누는 풀이
반환 배열은 [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]이며 length가 10입니다. 원래 message handler는 결과 DOM, 상태 DOM을 차례로 갱신한 다음 terminate를 호출하고 myWorker를 null로 바꿉니다. 버튼을 비활성화하거나 다시 활성화하는 처리는 원래 코드에 없습니다. ArrayBuffer를 실제 transfer했다면 송신 쪽 buffer는 detached되어 이전 데이터를 읽는 수단으로 사용할 수 없습니다. 결과 숫자를 얻는 연산과 전송 데이터의 소유권 변경은 같은 개념이 아닙니다. Worker 메시지·종료 모델에서는 계산 실행 환경과 메시지 전달을 별도로 다룹니다.
따라하기: Service Worker의 설치와 현재 페이지 제어를 구별하기
Service Worker 실습은 다른 앱의 캐시가 없는 전용 학습 사이트에서 시작합니다. 원래 sw.js의 캐시 목록에 든 /, /index.html, /styles.css, /main.js, /logo.png가 모두 성공 응답을 반환하는지 등록 전에 확인합니다. 없는 파일 하나를 넣으면 addAll이 실패해 설치가 끝나지 않을 수 있습니다. HTTPS의 secure context에서 실습하며, 로컬 개발에서는 loopback origin을 신뢰 가능한 출처로 다루는 경우가 있습니다. Secure Contexts의 출처 조건을 참고하고 window.isSecureContext와 navigator.serviceWorker 제공 여부도 확인합니다.
등록의 Promise가 끝났다는 사실만으로 지금 화면이 제어되고 있다고 결론 내리지 않습니다. 아래 코드를 등록하는 페이지에서 사용하면 제어 관계가 바뀌는 순간을 관찰할 수 있습니다. 별도 사이트용 배포 코드를 변경하는 실습은 아닙니다.
function showController() {
console.log('현재 controller:', navigator.serviceWorker.controller?.scriptURL ?? null);
}
if ('serviceWorker' in navigator) {
showController();
navigator.serviceWorker.addEventListener('controllerchange', showController);
}원래 activate handler는 clients.claim도 요청하지만, 등록 완료·활성화 완료·controllerchange는 서로 다른 관찰 시점입니다. 활성 상태와 controller를 확인한 뒤 네트워크를 끊고 미리 캐시한 index.html을 다시 요청해 보세요. 원래 fetch handler는 cache-first이므로 캐시한 문서는 반환할 수 있지만, 캐시에 없는 요청이 오프라인에서도 성공한다고 가정하지 않습니다. Service Worker의 상태와 client 제어 규칙에 해당합니다.
독립 연습
이미 버전 A가 페이지를 제어하고 있고, 새 버전 B의 precache는 성공했습니다. 기존 제어 페이지는 열려 있으며 B는 skipWaiting을 호출하지 않았습니다. B의 installed 상태와 registration.waiting을 관찰했다면, 현재 페이지의 controller가 즉시 B로 바뀌었다고 말할 수 있을까요? 별도 실험에서 B의 precache에 없는 파일이 하나 들어가 install의 waitUntil이 거부되었다면, B를 정상 waiting 버전으로 취급해도 될까요?
설치된 버전과 실제 제어자를 나누는 풀이
첫 경우 B는 대기 중이고 현재 제어자는 A입니다. 등록이나 설치 완료만으로 B의 제어를 단정할 수 없습니다. 기존 client 해제 등 활성화 조건과 새로운 제어 관계를 확인해야 합니다. 두 번째는 설치 실패이므로 B를 정상 waiting 버전으로 취급하지 않습니다. 기존 A가 있다는 조건도 B의 설치 실패와 구별합니다. 실행 중인 Service Worker가 이벤트 사이에 멈추는 현상은 버전이 redundant로 바뀌는 것과 다릅니다.