October 24, 2022
실무 적용을 위해 브라우저에서 제공하는 멀티스레딩 API 인 웹 워커를 정리한 글 입니다. 웹 워커는 CPU 부하량이 높은 작업을 별도의 스레드에서 처리할 수 있다는 장점이 있습니다. 그러면 메인 쓰레드는 UI 렌더링에 더 집중하여 안정적인 FPS(Frame Per Second)를 유지할 수 있습니다. 결과적으로 사용자에게 더 좋은 웹 페이지 경험을 제공할 수 있습니다!
프론트엔드 프레임워크에 삼대장이 있듯, 워커도 삼대장이 있습니다. 물론 점유율도 다양하구요. 하나씩 정리해봅시다.
단일 스크립트에서만 사용하는 스레드입니다. 보통 워커를 구현한다고 했을 때 가장 많이 사용하며 적용 또한 단순합니다. 메시지 패싱(message passing) 방식으로 메인 스레드와 워커 스레드가 서로 통신을 합니다.
// main.js
const worker = new Worker('worker.js')
worker.onmessage = msg => {
console.log(msg.data)
}
worker.postMessage('main → worker')
// worker.js
self.onmessage = msg => {
console.log(msg.data)
postMessage('worker → main')
}
// Console
main → worker
worker → main
worker.postMessage
worker.onmessage
worker.onerror
worker.onmessageerror
worker.terminate
워커를 생성하려면 아래와 같이 생성자 함수를 사용합니다. 각 인자를 살펴보겠습니다.
const worker = new Worker(filename, options)
filename: 워커가 동작할 코드의 URL 을 작성합니다. 동일 출처 정책을 따르기 때문에 같은 Origin 의 URL 만 가능합니다.
options: 객체 타입으로 워커의 여러 옵션을 지정할 수 있습니다.
credentials: 워커 파일을 로드할 때, HTTP 자격증명을 포함할 지 결정합니다. type 이 module 인 경우에만 사용됩니다.
자격증명 활성화 후 아무 정보없이 요청하면 위와 같은 경고 메시지가 발생된다
공유 워커는 윈도우 창, iframe 등 다른 브라우저 환경에서 접근 가능한 워커입니다.
공유 워커의 특징은 공유 워커를 생성한 컨텍스트가 종료되어도 다른 브라우저 환경이 존재한다면 공유 워커는 여전히 존재한다는 것입니다.
예시로 red.html 과 blue.html 에서 공유 워커를 사용하는 코드를 작성해봤는데요, 아래와 같습니다
// red.js
console.log('red.js')
const worker = new SharedWorker('shared-worker.js')
worker.port.onmessage = event => {
console.log('EVENT', event.data)
}
// blue.js
console.log('blue.js')
const worker = new SharedWorker('shared-worker.js')
worker.port.onmessage = event => {
console.log('EVENT', event.data)
}
// shared-worker.js
const ID = Math.floor(Math.random() * 999999)
console.log('shared-worker.js', ID)
const ports = new Set()
self.onconnect = event => {
const port = event.ports[0]
ports.add(port)
console.log('CONN', ID, port.size)
port.onmessage = event => {
console.log('MESSAGE', ID, event.data)
for (let p of ports) {
p.postMessage([ID, event.data])
}
}
}
결과를 보면 blue.js 에서 생성한 워커에 ‘hello’ 문자열을 전달하였지만 red.js 에도 동일한 ID, data 가 출력되는 것을 확인할 수 있습니다.
서비스 워커는 웹 사이트의 캐시를 관리하기 위한 워커입니다.
여러 개의 페이지와 연결할 수 있다는 점에서 공유 워커와 비슷합니다.
네트워크 연결이 끊겼을 때 브라우저가 정상적으로 웹페이지를 보여줄 수 있도록 캐시된 데이터를 서비스 워커가 리턴하는게 대표적인 용도입니다.
// main.js
navigator.serviceWorker.register('service-worker.js', { scope: '/' })
// contorllerchange 이벤트 리스너
navigator.serviceWorker.oncontrollerchange = () => {
console.log('controller change')
}
// 요청 함수
const makeRequest = async () => {
const result = await fetch('/data.json')
const payload = await result.json
console.log(payload)
}
‘/’
이므로 서비스 워커가 모든 범위를 제어하네요~// service-worker.js
let count = 0
self.oninstall = event => {
console.log('service worker install')
}
self.onactivate = event => {
console.log('service worker activate')
event.waitUntil(self.clients.claim())
}
self.onfetch = event => {
console.log('fetch', event.request.url)
if (event.request.url.endsWith('/data.json')) {
count++
event.respondWith(
new Response(JSON.stringify({ count }), {
headers: {
'Content-Type': 'application/json',
},
})
)
return
}
event.respondWith(fetch(event.request))
}
/data.json
으로 주소가 끝나는지 확인하여 응답을 JSON 형식으로 전달받을 것을 의미합니다. 그외의 요청에는 fetch 처리를 합니다.
parsed
installing
installed
activating
activated
redundant
이번에 웹 워커를 실무에 적용하기 앞서 이론적인 공부를 해봤는데요, 사실 저는 전용 워커만 사용해본 적이 있고 다른 공유 워커와 서비스 워커는 필요성을 못 느껴서 사용한 적이 없습니다. 이번 글에서는 웹 워커들을 종류별로 찍먹해본 느낌에 가깝습니다. 다음 글에서는 메인 스레드와 워커 간의 메시지 패싱에 관하여 정리하겠습니다. 훈수는 항상 환영합니다🤗