서버에서 응답을 받은 후에 다음 동작을 해야 하는데 동작이 가끔 느려 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를 실행할 수 있습니다.
메인 스크립트에서 워커에게 데이터를 전달할 수도 있고 완료 시 값을 반환할 수도 있습니다. 하지만 몇 가지 제한 사항이 있습니다.
그러나 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;
};
얼핏 보면 그럴듯해 보였는데 이걸 어디에 적용할지 생각하면 애매합니다. 애초에 프론트에서 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/