5. webworker
비동기 함수의 한계
- 만약 비동기 콜백 안에 오래 걸리는 작업이 있다면(엄청 큰 for loop 같이) 이벤트 루프를 해제하고 UI 랜더링을 하게 만드는 방법이 없을까?
- 몇몇 경우에서는 작업을 setTimeout으로 분리해 이벤트 루프 넣어 중간 중간 렌더링 할 시간을 벌어 주도록 트릭을 쓸 수 있었다.
web worker
- 웹 워커 또한 HTML5의 기능이다.
- SSE
- Geolocation
- Application cache
- Local Storage
- Drag and Drop
- Web Workers
- 웹 워커는 이벤트 루프 막힘 없이 코드를 실행 할 수 있도록 브라우저 안에 있는 스레드이다.
- 시간이 오래 걸리고 계산이 많이 요구되는 작업을 할 수 있게 해준다.
- 웹 워커는 자바스크립트의 기능이 아닌 브라우저의 기능이다.
- 노드js에서는 웹워커가 구현되지 않았고
cluster, child_process
라는 개념이 있다. - 웹 워커의 타입은 Dedicated Workers, Shared Workers, Service workers가 있다.
web worker type
- Dedicated Workers : 메인 프로세스에 의해서 인스턴스화 되고 커뮤니케이션 할 수 있다.
- Shared Workers : 서로 다른 윈도우, 아이프레임, 다른 워커간에 접근 할 수 있고 포트 객체를 통해 커뮤니케이션 할 수 있다.
- Service Workers : 원하는 페이지에 이벤트를 등록해서 프록시 처럼 웹페이지나 사이트를 컨트롤 할 수 있다. (Push Notifications, Cache, dominate Network Traffic)
Dedicated web worker 구현
- 웹 워커는 .js 파일로 http request에 포함 되어 있는데 web worker api로 리퀘스트를 숨길 수 있다.
- 브라우저의 독립적인 스레드로 분리된 파일에서 실행되야 한다.
var worker = new Worker('task.js');
- task.js에 접근하면 브라우저는 새 스레드를 생성하고 파일을 비동기로 다운로드 한다.
- 다운로드가 완료 되면 실행되면서 워커가 시작된다.
worker.postMessage();
- 웹워커와 페이지가 통신하기 위해서
postMessage()
메소드나BroadcastChannel()
이 필요하다. - 최신 브라우저는
JSON
을 파라미터로 써도 되지만 오래된 브라우저는string
만 지원한다.
postMessage()
를 이용한 방법
<button onclick="startComputation()">Start computation</button>
<script>
function startComputation() {
worker.postMessage({'cmd': 'average', 'data': [1, 2, 3, 4]});
}
var worker = new Worker('doWork.js');
worker.addEventListener('message', function(e) {
console.log(e.data);
}, false);
</script>
- doWork.js
self.addEventListener('message', function(e) {
var data = e.data;
switch (data.cmd) {
case 'average':
var result = calculateAverage(data); // Some function that calculates the average from the numeric array.
self.postMessage(result);
break;
default:
self.postMessage('Unknown command');
}
}, false);
self
,this
는 글로벌 스코프를 참조한다.- 워커를 멈추기 위해서는
worker.terminate()
,self.close()
BroadcastChannel()
를 이용한 방법
- Broadcast Channel는 더 일반적인 API이다.
- 모든 탭, 아이프레임, 같은 브라우저에서 커뮤니케이션이 가능하다.
var bc = new BroadcastChannel('test_channel');
// Example of sending of a simple message
bc.postMessage('This is a test message.');
// Example of a simple event handler that only
// logs the message to the console
bc.onmessage = function (e) {
console.log(e.data);
}
// Disconnect the channel
bc.close()
메세지 전송 방법
- Copying the message : 메세지는 serialize, 복사, 전송, de-serialize 된다. 페이지와 worker는 같은 인스턴스를 공유 하지 않고, 각자 생성해서 중복된 결과를 가진다. 대부분의 브라우저는 자동으로 json을 인코딩/디코딩하는 기능이 있어서 이 처리는 오버헤드가 발생하고 메세지가 클수록 증가한다.
- Transferring the message : 오리지날 센더는 한번 보내면 더이상 사용 할 수 없다. 데이터는 거의 동시에 베껴진다. ArrayBuffer만 전송 가능하다.
웹 워커 기능
웹 워커는 자바스크립트 바운터리 안에서만 사용 가능하다.
navigator
객체location
객체XMLHttpRequest
setTimeout()/clearTimeout()
과setInterval()/clearInterval()
- The Application Cache
- Importing external scripts using importScripts()
- 다른 웹 워커 생성
웹 워커의 한계
몇몇 javascript 기능에 접근하지 못하는 한계가 있다.
DOM
window 객체
document 객체
parent 객체
웹 워커는 돔을 조작할 수 없다. 별도의 컴퓨팅 머신으로 사용하는고 UI 변경 결과를 페이지 코드에 전달해 사용해야 한다.
에러 핸들링
- 웹워커로 자바스크립트 에러를 처리 할 수 있다.
- 필요 프로퍼티
- filename : 웹 워커 js 파일
- lineno : 에러 난 라인 번호
- message : 에러 설명
function onError(e) {
console.log('Line: ' + e.lineno);
console.log('In: ' + e.filename);
console.log('Message: ' + e.message);
}
var worker = new Worker('workerWithError.js');
worker.addEventListener('error', onError, false);
worker.postMessage(); // Start worker without a message.
웹 워커의 활용 사례
- Ray tracing : ray tracing이란 빛의 경로를 추적하며 이미지를 생성하는 렌더링 기술이다. Ray tracing은 빛의 경로 계산하기 위해서 CPU를 많이 사용한다. 이 로직을 웹 워커에 넣어놓으면 UI block이 되지 않기 때문에 더 좋은 결과를 얻을 수 있다.
- Encryption : 암호화 할 데이터가 많고 빈번하다면 꽤나 시간 소비가 많이 일어난다. 웹 워커는 DOM 에 접근을 못하기 때문에 순수 알고리즘 작업을 하기 좋다.
- Prefetching data : 웹 사이트를 최적화하거나 데이터 로딩 시간을 개선하기 위해서 웹 워커에서 데이터 로드를 맡기고 필요할 때 꺼내 쓸 수 있다.
- Progressive Web Apps : PWA는 네크워크 커넥션이 불안 할 때도 빠르게 로드해서 브라우저에 저장한다. 이 일을 웹 워커에서 한다면 UI blocking이 일어나지 않을 것이다.
- Spell checking : 기본적으로 스펠 체크는 프로그램이 dictionary 파일을 읽고 dictionary를 검색 트리로 파싱된다. 프로그램은 서치트리에서 존재를 확인하고 만약 없다면 대체 스펠링을 제공한다.
서비스 워커 라이프 사이클
- 서비스 워커는 글로벌 스크립트에 속해 있다.
- 특정 웹 페이지에 국한 되지 않는다.
- DOM에 접글 할 수 없다
- 서비스 워커에 메인 기능은 오프라인 경험을 지원하기 위함이다.
Download
- 서비서 워커가 있는 js 파일을 브라우저가 다운로드 할 때
Installation
- 서비스워커 설치를 위해서 자바스크립트 코드를 등록 해야한다.
- 서비스 워커가 등록 되었을 때 브라우저는 background에서 설치를 시작한다.
- 현재 환경이 서비스워커를 지원하면 서비스워커를 등록한다.
- 이미 등록되어 있으면 페이지를 로드할 때 마다 register()를 호출해도 된다. 설치는 한번만 발생 한다.
- register는 꼭 서비스 워커 파일에 등록 되어 있어야 한다.
<!DOCTYPE html> An image will appear here in 3 seconds: <script> navigator.serviceWorker.register('/sw.js') .then(reg => console.log('SW registered!', reg)) .catch(err => console.log('Boo!', err)); setTimeout(() => { const img = new Image(); img.src = '/dog.svg'; document.body.appendChild(img); }, 3000); </script>
- sw.js파일을 등록하고 이미지 추가하기
Activation
- 서비스워커가 등록 된 후 이전 캐쉬를 관리하기 위한 단계이다.
- 한번 작동 되면 서비스 워커는 그 스코프 아래에 있는 모든 페이지를 컨트롤 하기 시작하고 처음 등록된 페이지는 그 페이지가 다시 등록되기 전까지 다시 컨트롤 하지 않는다.
self.addEventListener('install', event => {
console.log('V1 installing…');
// 설치 완료 시점과 성공 여부를 브라우저에 알립니다.
event.waitUntil(
caches.open('static-v1').then(cache => cache.add('/cat.svg'))
);
});
//push 및 sync와 같은 함수 이벤트를 처리할 준비가 되면 발생
self.addEventListener('activate', event => {
console.log('V1 now ready to handle fetches!');
});
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// 새로고침 시 /dog.svg' 호출 했을 때 캐시 해놓은 /cat.svg 제공
if (url.origin == location.origin && url.pathname == '/dog.svg') {
event.respondWith(caches.match('/cat.svg'));
}
});
- Updating a Service Worker
- 현재 서비스 워커 파일과 비교해 바이트 하나만 달라도 변경되었다고 판단해 서비스 워커를 시작한다.
- 이 떄 설치되는 서비스 워커는 새로 설치된다. 하지만 이전 서비스 워커가 페이지를 제어하고 있다면 새 서비스 워커는 waiting 상태가 된다.
- 모든 브라우저를 다 닫지 않으면 이전 서비스 워커로 잡힘.
//새로운 이름으로 설정
const expectedCaches = ['static-v2'];
self.addEventListener('install', event => {
console.log('V2 installing…');
// cache a horse SVG into a new cache, static-v2
event.waitUntil(
caches.open('static-v2').then(cache => cache.add('/horse.svg'))
);
});
self.addEventListener('activate', event => {
// expectedCaches 가 아닌거 지우기
event.waitUntil(
caches.keys().then(keys => Promise.all(
keys.map(key => {
if (!expectedCaches.includes(key)) {
return caches.delete(key);
}
})
)).then(() => {
console.log('V2 now ready to handle fetches!');
})
);
});
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (url.origin == location.origin && url.pathname == '/dog.svg') {
event.respondWith(caches.match('/horse.svg'));
}
});