javascript의 이벤트루프 동작원리

권세진·2021년 2월 13일
2

js이론

목록 보기
2/9

@해당 글은 https://meetup.toast.com/posts/89 를 참고해 정리한 글임을 밝힙니다.

@이 글에서는 브라우저에서의 이벤트 루프만 다룹니다.

🎡 이벤트 루프 구조

  • 호출 스택(Call Stack)

    js 가 실행할 함수 순서를 담아두는 스택
    하나의 호출 스택을 사용하기 때문에
    js 가 단일 호출 스택이라고 불리는 이유가 이것이다.
    (ES6부터는 조금 달라졌다고 하지만 우선 이렇게 알아두자)

  • 태스크 큐(Task Queue)

    콜백함수들이 대기하는 큐
    특정 조건이 만족되면 큐에 콜백함수들이 추가된다.
    js 엔진의 바깥에 존재

  • 이벤트 루프(Event Loop)

    호출 스택이 비워질 때마다 테스크 큐에서 콜백함수를 꺼내와서 실행
    js 엔진의 바깥에 존재

  //내부적으로 이렇게 동작한다고 한다.
  while(queue.waitForMessage()){
    queue.processNextMessage();
  }
  • 브라우저 타이머(web API에 포함됨)

    시간을 잰다.

🧶 비동기 이벤트의 동작 순서

  1. 모든 비동기 API들은 작업(or 조건)이 완료되면 콜백 함수를 테스크 큐에 추가한다.
  2. 이벤트 루프는 '현재 실행중인 태스크가 없을 때'(주로 호출 스택이 비워졌을 때) 태스크 큐의 첫 번째 태스크를 꺼내와 호출 스택에 올려 실행한다.
  • ex) setTimeout (addEventListener도 마찬가지)
    1. web API의 timer가 특정 시간이 되면 콜백 함수를 테스크 큐에 추가한다.
    2. js 엔진의 호출 스택이 비워져 있음을 확인하고 비어있으면 테스크 큐에서 테스크를 하나 꺼내와 호출 스택에 올린다.
    3. 호출 스택에서 순차적으로 실행

😅 비동기 API의 try catch 문제

$('.btn').click(function() { // (A)
    try {
    	//서버의 응답을 요청하는 코드인듯
        $.getJSON('/api/members', function (res) { // (B) 
            // 에러 발생 코드
        });
    } catch (e) {
        console.log('Error : ' + e.message);
    }
});

다음과 같은 코드는 try catch로 잡아낼 수 없음

브라우저는 서버의 응답을 받아 (B)를 테스크 큐에 추가하고, 호출 스택이 비어있으면 이벤트 루프는 (B)를 실행시킨다.

그런데 문제는 (B)는 이미 (A)의 컨텍스트 내에 있지 않다는 것. 그래서 (B)는 try catch에 영향을 받지 않는다.

🤨 setTimeout(fn, 0) 은 즉시 실행을 의미하는가?

0은 즉시를 의미하지 않는다.

$('.btn').click(function() {
    showWaitingMessage(); // 비동기 함수, 로딩메세지를 보여주는 함수
    longTakingProcess();
    hideWaitingMessage();
    showResult();
});

먼저 showWaittingMessage()의 렌더링 엔진이 렌더링 함수를 테스크 큐에 보낸다.

하지만 longTakingProcess() 와 이하 아래의 함수들이 호출 스택에 남아있어서 로딩 메세지 렌더링 함수는 showResult()가 실행되고 나서야 호출된다.

이런 문제를 막기위해 setTimeout(fn, 0)을 사용한다.
아래는 해결 코드

$('.btn').click(function() {
    showWaitingMessage(); // 비동기 함수, 로딩메세지를 보여주는 함수
    setTimeout(function() {
      longTakingProcess();
      hideWaitingMessage();
      showResult();
    }, 0);
});

😱 마이크로 테스크?

마이크로 테스크는 일반 테스크 보다 우선 순위가 높은 테스크라고 할 수 있다.

마이크로 테스크는 일반 테스크 큐에 저장되지 않고 마이크로 테스크 큐에 따로 저장된다.

아래 코드를 보자

  setTimeout(function() { // (A)
      console.log('A');
  }, 0);
  Promise.resolve().then(function() { // (B)
      console.log('B');
  }).then(function() { // (C)
      console.log('C');
  });

이 코드의 실행 순서는 B->C->A 이다.
Promise의 then() 메서드는 콜백을 마이크로 테스크 큐에 저장한다. 따라서

마이크로 테스크 큐 : B , C

테스크 큐 : A

이렇게 저장되어 B->C->A 순서로 실행된다는 것이다.

profile
상상을 현실로 꺼내길 좋아하는 프론트엔드 개발자입니다.

0개의 댓글