본문으로 바로가기

일반적으로 자바스크립트는 다음 순서로 코드를 실행한다.

 

call stack => microtask queue => task queue

콜백함수를 태스크 큐에 넣는 함수들 : setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI 렌더링

콜백함수를 마이크로태스크 큐에 넣는 함수들 : process.nextTick, Promise, Object.observe, MutationObserver.

 

이 순서를 잘 생각해보면 마이크로태스크 큐에 너무 많은 것을 집어 넣는다면, UI 렌더링이 그만큼 늦어지게 된다는 것을 추측할 수 있습니다.

 

한편, 위 내용에 근거하여 작성한 아래 코드의 출력 결과가 다음과 같다.

먼저, 콜스택을 처리한 후, 마이크로 태스크 큐를 처리한 뒤, 마지막으로 태스크 큐를 처리하는데 queue 자료구조이므로 선입선출 원리에 따라 requestAnimationFrame가 먼저 실행되고, 그 다음에 setTimeout이 실행됩니다.

console.log("call stack");
setTimeout(() => console.log("setTimeout"), 0);
queueMicrotask(() => console.log("microtask queue"));
requestAnimationFrame(() => console.log("anim"));

// output
call stack
microtask queue
anim
setTimeout

 

그러나 이러한 동작을 예측 가능하게 짜는 것은 생각보다 어려운 일이다.

위의 코드는 매우 단순한 형태에 비해 실제 프로덕트에서 발생하는 콜백들은 저렇게 단순한 형태로 정갈하게 담겨져 있기 않기 때문이다.

 

이러한 문제를 손쉽게 해결해주는 방법으로, rxjs의 스케쥴러를 이용하는 방법이다.

 

https://rxjs-dev.firebaseapp.com/guide/scheduler

 

쉽게 요약 하자면

 

queueScheduler : 태스크 큐. setTimeout, setInteravl로 생각하면 편함.

asapScheduler : 마이크로 태스크 큐.

animationFrameScheduler : requestAnimationFrame임.

queueScheduler : 곧바로 콜스택에 담김. 사용할 일이 별로 없음.

 

 

asyncScheduler

 

아주 쉽게 사용할 수 있습니다. setTimeout과 같은 것이라 봐도 일단은 무방합니다.

// f(work, delay?, state?)
// asyncScheduler.schedule(console.log, 0, "async schedule!"); 이런 식으로도 사용 가능

asyncScheduler.schedule(() => console.log("async schedule!"), 0);
console.log("call stack");

// output
// call stack
// async schedule!

 

schedule 메서드는 subscription을 반환합니다. 즉, unsubscribe를 통해서 '취소'시킬 수 있다는 점이 장점입니다.

아래 코드는 콜스택에 sub.unsubscribe()가 먼저 발동되어  async schedule이 출력되지 않습니다.

const sub = asyncScheduler.schedule(console.log, 0, "async schedule!");
console.log("call stack");

sub.unsubscribe();

// output
// call stack

 

asyncScheduler를 obsv$와 같이 사용하는 방법은 scheduled를 사용하면 됩니다.

pipe를 통해 operator로 처리하고 싶다면 observeOn을 사용합니다.

아래 코드는 4, 5, 6을 먼저 처리하고 이후에 1, 2, 3을 처리합니다.

scheduled([1, 2, 3], asyncScheduler).subscribe(obv);
of(4, 5, 6).subscribe(obv);

// 같은 내용. 3초 딜레이만 줌
of(1, 2, 3).pipe(observeOn(asyncScheduler, 3000)).subscribe(obv);
of(4, 5, 6).subscribe(obv);

 

 

asapScheduler

 

마이크로 태스크 큐와 비슷합니다. 실행해보면, asyncScheduler보다 앞서서 작동하는 것을 확인할 수 있습니다.

일반적인 태스크 큐보다 빨리 동작하니, asap 라는 이름이 붙은 것 같습니다.

asyncScheduler.schedule(() => console.log("async schedule"), 0);
asapScheduler.schedule(() => console.log("asap schedule"), 0);
console.log("call stack");

// call stack
// asap schedule
// async schedule

 

먼저 실행된다고 하니, 조금 감이 안 올 수 있는데, 태스크 큐에 해당하는 UI 렌더링과 결합해서 보면 이해가 됩니다.

const numberDom = document.querySelector(".number");
console.log(numberDom);

// asap. 마이크로 태스크 큐. 이미 숫자가 다 올라간 후 UI를 그리기 때문에 시작부터 1000이 보임
range(1, 1000)
  .pipe(observeOn(asapScheduler, 0))
  .subscribe((x) => (numberDom.textContent = x));

// async. 같은 태스크 큐이며 숫자가 1부터 1000까지 올라가는 모습을 확인할 수 있음
range(1, 1000)
  .pipe(observeOn(asyncScheduler, 0))
  .subscribe((x) => (numberDom.textContent = x));

 

 

animationFrameScheduler

브라우저가 부드러운 애니메이션을 그릴 수 있도록 도와주는 녀석입니다.

requestAnimationFrame의 rxjs 버전이라고 생각하시면 편합니다

animationFrameScheduler.schedule(
  function (position) {
    console.log(position);
    ball.style.transform = `translate3d(0, ${position}px, 0)`;
    
    // 300 전까지는 재귀적으로 계속 돌리기
    if (position < 300) this.schedule(position + 1);
  }, 0, 0
);

 

위 코드를 보다 rx적이게 바꿔보자면 다음과 같습니다. 이렇게 써야죠.

interval(0, animationFrameScheduler)
  .pipe(takeWhile((v) => v < 300))
  .subscribe({
    next: (v) => {
      ball.style.transform = `translate3d(0, ${v}px, 0)`;
    },
  });

 

 

queueScheduler

이 녀석은 곧바로 호출 스택에 담깁니다.

아래 처럼 작성해도 그냥 순서대로 호출됩니다. 

이럴 거면 왜 쓰냐구요? 넵 쓸일이 없습니다.

queueScheduler.schedule(() => console.log("first queue"), 0);
console.log("call stack");

// first queue
// call stack

 

다만 아래와 같이, 비동기가 아닌, 동기적으로 코드를 작성하되, 순서를 명확히 하고 싶을 때 사용할 수는 있습니다.

에... 근데 이것도 조금 의미가 없는게 그냥 순서대로 작성하면 되는데...

queueScheduler.schedule(() => {
  queueScheduler.schedule(() => console.log("inner queue"), 0);
  console.log("first queue");
}, 0);
console.log("call stack");

// first queue
// inner queue
// call stack

 

 

 

 

 

 

ref)

https://baeharam.netlify.app/posts/javascript/JS-Task%EC%99%80-Microtask%EC%9D%98-%EB%8F%99%EC%9E%91%EB%B0%A9%EC%8B%9D

 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체