웹 워커와 서비스 워커 (1)

Caesars·2022년 11월 24일
1

JS

목록 보기
5/8

서버에서 응답을 받은 후에 다음 동작을 해야 하는데 동작이 가끔 느려 timeout을 설정하고 싶다는 요청이 있었습니다. 하지만 XHR은 동기식과 timeout을 동시에 적용할 수가 없어서 고민하고 있었는데 누군가가 웹 워커에서는 가능하다고 하더군요. 결국 적용하지는 못했지만 워커에 관해 공부하면서 알게 된 내용을 정리했습니다.


워커에서 서버 호출

function mainScript(){
    let request = new XMLHttpRequest();

    request.open("POST", serverUrl, false);	//동기 설정
    request.timeout = 1000;
    request.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');

    request.onload = function() {};

    request.send(obj.data.body);
    request.ontimeout = request.onerror = function(){};
}

Uncaught DOMException: Failed to set the 'timeout' property on 'XMLHttpRequest': Timeouts cannot be set for synchronous requests made from a document.

위 코드는 실행하면 바로 에러를 냅니다. 동기식과 timeout을 동시에 적용할 수 없다는 것인데 거의 비슷한 코드라도 워커에서는 정상적으로 동작합니다.

function getWorkerURL(data) {
    let content = `self.onmessage = function(obj) {
        let request = new XMLHttpRequest();
        request.open("POST", serverUrl, false);	//동기 설정
        request.timeout = 1000;
        request.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
    
        request.onload = function() {
            if (this.status == 200 ) {
                let jsonResponse = JSON.parse(this.response);
                postMessage(jsonResponse);
            }
        };
    
        request.send(data);
        request.ontimeout = request.onerror = function(){};
      };`;
    return URL.createObjectURL( new Blob( [ content ], { type: "application/javascript" } ) );
}


function mainScript(){
  
    let worker_url = getWorkerURL();
    let worker = new Worker( worker_url );

    worker.postMessage("body" : jsonStringify(body)});  // 워커에 메시지를 보낸다.

    worker.onmessage = function(e) {
        return e.data;
    };
}

물론 백그라운드에서 서버를 호출하는 것이 웹 워커의 전부는 아닙니다. 웹 워커의 진가는 싱글쓰레드 방식인 js를 동시 처리가 가능하도록 만드는 것이라고 봅니다.

웹 워커란?

일반적으로 웹 페이지의 js 코드는 UI와 동일한 스레드에서 실행됩니다. 만약 시간이 오래 걸리는 작업을 처리하는 버튼을 클릭하면 UI가 정지됩니다. 웹 워커를 사용하면 일부 스크립트가 실행되는 경우에도 UI가 응답성을 유지하도록 백그라운드에서 js를 실행할 수 있습니다.
메인 스크립트에서 워커에게 데이터를 전달할 수도 있고 완료 시 값을 반환할 수도 있습니다. 하지만 몇 가지 제한 사항이 있습니다.

  • 페이지의 window나 DOM 요소에 액세스할 수 없습니다.
  • 페이지에서 전역 변수 및 JavaScript 함수에 액세스할 수 없습니다.
  • alert() 또는 confirm() 함수를 호출할 수 없습니다.

그러나 setTimeout(), setInterval()과 같은 함수는 사용가능합니다. 기준이 뭔지는 좀 더 찾아봐야겠습니다.

비교 예시

캔버스에 시계가 돌아가는 애니메이션을 구현하고 1000만개 배열을 소팅하는 작업을 메인 스크립트와 웹 워커에 추가했습니다. 메인 스크립트에서 작업시 UI가 순간 멈추지만 웹 워커에서 작업할 경우 정상적으로 동작하는 것을 확인했습니다.

function clock() {
  const now = new Date();
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");
  ctx.save();
  ctx.clearRect(0, 0, 150, 150);
  ctx.translate(75, 75);
  ctx.scale(0.4, 0.4);
  ctx.rotate(-Math.PI / 2);
  ctx.strokeStyle = "black";
  ctx.fillStyle = "white";
  ctx.lineWidth = 8;
  ctx.lineCap = "round";

  const sec = now.getSeconds();

  ctx.fillStyle = "black";

  // Write seconds
  ctx.save();
  ctx.rotate((sec * Math.PI) / 30);
  ctx.strokeStyle = "#D40000";
  ctx.fillStyle = "#D40000";
  ctx.lineWidth = 6;
  ctx.beginPath();
  ctx.moveTo(-30, 0);
  ctx.lineTo(83, 0);
  ctx.stroke();
  ctx.beginPath();
  ctx.arc(0, 0, 10, 0, Math.PI * 2, true);
  ctx.fill();
  ctx.beginPath();
  ctx.arc(95, 0, 10, 0, Math.PI * 2, true);
  ctx.stroke();
  ctx.fillStyle = "rgba(0, 0, 0, 0)";
  ctx.arc(0, 0, 3, 0, Math.PI * 2, true);
  ctx.fill();
  ctx.restore();

  ctx.beginPath();
  ctx.lineWidth = 14;
  ctx.strokeStyle = "#325FA2";
  ctx.arc(0, 0, 142, 0, Math.PI * 2, true);
  ctx.stroke();

  ctx.restore();

  window.requestAnimationFrame(clock);
}

window.requestAnimationFrame(clock);

function getWorkerURL() {
	let content = `self.onmessage = function() {
        const numbers = [...Array(10000000)].map(e => ~~(Math.random() * 1000000));
		numbers.sort();
		postMessage(numbers);
      }`;
    return URL.createObjectURL( new Blob( [ content ], { type: "application/javascript" } ) );
}

let worker_url = getWorkerURL();
let worker = new Worker( worker_url );

worker.postMessage({});  // 워커에 메시지를 보낸다.

worker.onmessage = function(e) {
    return e.data;
};

웹 워커와 서비스 워커

공통점

  • 메인 스레드 블록 없이 추가 스레드에서 실행
  • window나 document객체에 접근 불가

차이점

  • 한 페이지 내에서 여러 개의 웹 워커를 생성가능한 반면 단일 서비스 워커는 모든 활성 탭을 제어.
  • 웹 워커의 수명은 속한 탭과 결합되는 반면, 서비스 워커의 수명은 독립적.
    탭을 닫으면 웹 워커는 종료되지만, 서비스 워커는 백그라운드에서 계속 실행될 수 있다.

마치며

얼핏 보면 그럴듯해 보였는데 이걸 어디에 적용할지 생각하면 애매합니다. 애초에 프론트에서 1000만 배열을 소팅할 정도의 무거운 작업을 하면 안되니까요. 백그라운드에서 동기화 작업 정도는 가능할 듯 합니다. 기왕 알게됬으니 다음 장에서는 서비스 워커를 사용해 간단한 웹 푸시 서비스까지 만들어보도록 하겠습니다.

참고

https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations
https://benestudio.co/web-workers-in-javascript-and-when-to-use-them/

profile
잊기전에 저장

0개의 댓글