로우 레벨 로 살펴보 는 Node . js 이벤트 루프 - Evan ' s Tech Blog 
 이 포스팅 은 2018 년 2 월 19 일 에 Paul Shan 이 작성 한 Node . js event loop workflow & lifecycle in low level 를 번역 한 글 입니다 . 의역 이 있 을 수 있 습니다 . 
 왜 이 포스팅 을 작성 하 게 되 었 나 ? 
 만약 에 여러분 이 구글 에서 node . js event loop 를 검색 하 면 나오 는 대부분 의 아티 클 들 은 자세 한 내용 을 설명 해 주 지 않 는다 . ( 그 들 은 매우 거시 적 으로 만 이 과정 을 묘사 하 려고 한다 . ) 
 위 그림 은 구글 에서 nodejs event loop 를 검색 했 을 때 나오 는 이미지 들 을 캡쳐 한 것 이 다 . 그리고 대다수 의 이미지 결과 들 은 잘못 되 었 거나 실제 이벤트 루프 가 어떻 게 작동 하 는지 에 대해서 거시 적 으로 만 설명 하 고 있 다 . 이런 방식 의 설명 들 때문 에 개발자 들 은 종종 이벤트 루프 에 대한 잘못 된 이해 를 하 게 된다 . 아래 설명 할 몇 가지 개념 은 개발자 들 이 잘못 알 고 있 는 몇 가지 개념 들 이 다 . 
 대표 적 인 잘못 된 개념 들 
 이벤트 루프 는 자바 스크립트 엔진 내부 에 있 다 
 대표 적 인 잘못 된 개념 들 중 하나 는 바로 이벤트 루프 가 자바 스크립트 엔진 ( V 8 , Spider Monkey 등 ) 의 일부 라는 것 이 다 . 하지만 이벤트 루프 는 단지 자바 스크립트 코드 를 실행 하 기 위해 자바 스크립트 엔진 을 이용 하 기 만 할 뿐 이 다 . ( 역주 : 실제로 V 8 엔진 에 는 이벤트 루프 를 관리 하 는 코드 가 없 다 . Node . js 나 브라우저 가 이벤트 루프 를 담당 하 는 것 ) 
 이벤트 루프 는 하나 의 스택 또는 하나 의 큐 로 만 작동 한다 
 먼저 , 이벤트 루프 에 작업 을 담 아 놓 는 스택 같 은 것 은 존재 하 지 않 는다 . 그리고 이벤트 루프 가 작동 하 는 과정 은 여러 개 의 큐 ( 자료 구조 에서 의 그 큐 맞 다 . ) 를 사용 하 는 복잡 한 과정 이 다 . 그러나 대부분 의 개발자 들 은 자바 스크립트 의 모든 콜백 이 단 하나 의 큐 만 사용 하 여 수행 된다고 알 고 있 는데 , 이것 은 완전히 잘못 된 생각 이 다 . 
 이벤트 루프 는 여러 개 의 스레드 에서 실행 된다 
 Node . js 이벤트 루프 의 잘못 된 다이어그램 들 때문 에 우리 는 한 개 의 스레드 가 자바 스크립트 의 실행 을 담당 하 고 다른 한 개 는 이벤트 루프 를 담당 하 는 , 총 두 개 의 스레드 가 있 다고 생각 하 게 되 었 다 . ( 필자 도 자바 스크립트 뉴비 시절 에 그렇게 생각 했 다 . ) 
 그러나 실제로 는 단일 스레드 로 이 모든 것 을 처리 한다 . 
 setTimeout 은 일부 비동기 OS API 와 관련 있 다 . 
 또 다른 큰 오해 는 setTimeout 의 딜레이 가 끝 났 을 때 콜백 이 외부 의 요인 으로 인해 ( OS 나 커널 같 은 ) 의해 어떤 작업 큐 에 들어가 게 된다고 생각 하 는 것 이 다 . 하지만 이벤트 루프 에 이런 외부 의 요인 같 은 건 없 다 . 우리 는 밑 에서 이 메커니즘 에 대해서 좀 더 자세히 알아볼 것 이 다 . 
 setImmediate 의 콜백 은 작업 큐 의 가장 첫 번 째 에 위치 한다 
 보통 일반 적 인 이벤트 루프 에 대한 설명 들 은 하나 의 큐 만 가지 고 설명 을 진행 하 기 때문 에 , 몇몇 개발자 들 은 setImmediate ( ) 가 콜백 을 작업 큐 의 가장 앞쪽 에 배치 하 는 API 라고 생각 하 게 된다 . 하지만 이것 은 완전히 틀린 생각 이 며 , 모든 작업 큐 들 은 FIFO ( First In First Out ) 로 만 작동 한다 . ( 역주 : 큐 에 들 어 있 는 작업 의 포지션 을 절대 변경 하 지 않 는다는 것 이 다 . 무조건 큐 에 먼저 들어간 작업 이 먼저 실행 된다 . ) 
 이벤트 루프 의 구조 
 일단 이벤트 루프 의 구조 를 이해 하 기 위해서 는 이벤트 루프 의 흐름 에 대해서 알 고 있 어야 한다 . 이미 한 번 언급 했 듯이 , 거시 적 인 하나 의 큐 만 보 는 것 은 이벤트 루프 를 이해 하 는 데 별로 도움 이 되 지 않 는다 . 아래 그림 이 이벤트 루프 를 제대로 설명 한 그림 이 다 . 
 이 그림 에 표기 된 각각 의 박스 는 특정 작업 을 수행 하 기 위한 페이즈 들 을 의미 한다 . 각 페이즈 는 각자 하나 의 큐 를 가지 고 있 으며 , 자바 스크립트 의 실행 은 이 페이즈 들 중 Idle , prepare 페이즈 를 제외 한 어느 단계 에서 나 할 수 있 다 . ( 이해 를 돕 기 위해 큐 라고 설명 했 지만 사실 실제 자료 구조 는 큐 가 아닐 수 도 있 다 . ) 
 그리고 이 그림 에서 nextTickQueue 와 microTaskQueue 를 볼 수 있 는데 , 이 큐 들 은 이벤트 루프 의 일부 가 아니 며 , 이 큐 들 에 들 어 있 는 작업 또한 어떤 페이즈 에서 든 실행 될 수 있 다 . 또한 이 큐 들 에 들 어 있 는 작업 은 가장 높 은 실행 우선 순위 를 가지 고 있 다 . 
 이제 우리 는 이벤트 루프 가 각자 다른 여러 개 의 페이즈 들 과 큐 들 의 조합 으로 이루어져 있 다는 것 을 알 게 되 었 다 . 이제 각각 의 페이즈 가 어떤 작업 을 수행 하 는 지 알아보 자 . 
 Timer phase 는 이벤트 루프 의 시작 을 알리 는 페이즈 이 다 . 이 페이즈 가 가지 고 있 는 큐 에 는 setTimeout 이나 setInterval 같 은 타이머 들 의 콜백 을 저장 하 게 된다 . 이 페이즈 에서 바로 타이머 들 의 콜백 이 큐 에 들어가 는 것 은 아니 지만 타이머 들 을 min - heap 으로 유지 하 고 있 다가 실행 할 때 가 된 타이머 들 의 콜백 을 큐 에 넣 고 실행 하 는 것 이 다 . 
 역주 : 힙 ( Heap ) 은 완전 이진트리 의 일종 으로 어느 정도 의 느슨 한 정렬 을 사용 하 여 값 들 의 집합 에서 최대 값 이나 최소 값 을 찾아내 는 작업 에 특화 되 어 있 다 . min - heap 은 상위 레벨 의 노드 가 하위 레벨 의 노드 들 보다 작 거나 같 은 구조 이 므로 타이머 가 실행 되 어야 하 는 순서 대로 저장 하 기 에 알맞 는 자료 구조 라고 할 수 있 다 . 
 그리고 큐 에 콜백 을 넣 는다는 것 은 , 이 콜백 들 을 실행 하 겠 다는 것 을 의미 하 므로 타이머 가 생성 되 지 마자 큐 에 콜백 을 넣 는 것 이 아니 라 별도 의 힙 에 타이머 를 저장 하 고 나 서 매 Timer phase 때 어떤 타이머 가 실행 할 때 가 되 었 는지 를 검사 한 후 , 실행 되 어야 하 는 콜백 만 큐 에 넣 는다는 것 이 다 . 이 과정 에 대한 자세 한 내용 은 밑 에서 다시 설명 하 겠 다 . 
 이 페이즈 에서 는 이벤트 루프 의 pending _ queue 에 들 어 있 는 콜백 들 을 실행 한다 . 이 큐 에 들어와 있 는 콜백 들 은 현재 돌 고 있 는 루프 이전 에 한 작업 에서 이미 큐 에 들어와 있 던 콜백 들 이 다 . 예 를 들 어 여러분 이 TCP 핸들러 콜백 함수 에서 파일 에 뭔가 를 썼 다면 TCP 통신 이 끝나 고 파일 쓰 기 도 끝나 고 나 서 파일 쓰 기 의 콜백 이 이 큐 에 들어오 는 것 이 다 . ( 파일 쓰 기 는 보통 비동기 로 이루어진다 . ) 또한 에러 핸들러 콜백 도 pending _ queue 로 들어오 게 된다 . 
 이름 은 Idle phase 이 지만 이 페이즈 는 매 Tick 마다 실행 된다 . Prepare phase 또한 매 폴링 ( Polling ) 때 마다 실행 된다 . 어쨌든 이 두 개 의 페이즈 는 이벤트 루프 와 직접 적 인 관련 이 있 다기 보다 는 Node . js 의 내부 적 인 관리 를 위한 것 이 기 때문 에 이 포스팅 에서 는 설명 하 지 않 는다 . 
 필자 가 생각 하 기 에 전체 이벤트 루프 중 가장 중요 한 페이즈 는 바로 이 Poll phase 라고 생각 한다 . 이 페이즈 에서 는 새로운 수신 커넥션 ( 새로운 소켓 설정 등 ) 과 데이터 ( 파일 읽 기 등 ) 를 허용 한다 . 우리 는 여기 서 일어나 는 일 을 크 게 두 가지 로 나눠 볼 수 있 다 . 
 만약 watch _ queue ( Poll phase 가 가지 고 있 는 큐 ) 가 비 어 있 지 않 다면 , 큐 가 비 거나 시스템 최대 실행 한도 에 다다를 때 까지 동기 적 으로 모든 콜백 을 실행 한다 . 
 일단 watch _ queue 가 비 어 있 다면 , Node . js 는 곧바로 다음 페이즈 로 넘어가 는 것 이 아니 라 약간 대기 시간 을 가지 게 된다 . Node . js 가 기다리 는 시간 은 여러 가지 요인 에 따라 계산 되 는데 , 이 부분 은 밑 에서 따로 설명 하 도록 하 겠 다 . 
 Poll phase 의 다음 페이즈 는 바로 setImmediate 의 콜백 만 을 위한 페이즈 인 Check phase 이 다 . 이렇게 얘기 하 면 보통 하 시 는 질문 은 , 왜 setImmediate 의 콜백 만 을 위한 큐 인가요 ? 이 다 . 음 , 그건 밑 에서 필자 가 워크 플로우 섹션 에서 다시 얘기 할 Poll phase 에서 수행 하 는 행동 들 때문 이 기 도 하 다 . 일단 지금 은 Check phase 가 setImmediate 의 콜백 전용 단계 라는 사실 만 기억 하 고 있 자 . 
 nextTickQueue 와 microTaskQueue 
 nextTickQueue 는 process . nextTick ( ) API 의 콜백 들 을 가지 고 있 으며 , microTaskQueue 는 Resolve 된 프로 미스 의 콜백 을 가지 고 있 다 . 
 이 두 개 의 큐 는 기술 적 으로 이벤트 루프 의 일부 가 아니 다 . 즉 , libUV 라이브러리 에 포함 된 것 이 아니 라 Node . js 에 포함 된 기술 이 라는 것 이 다 . 이 친구 들 이 가지 고 있 는 작업 들 은 현재 실행 되 고 있 는 작업 이 끝나 자마자 호출 되 어야 한다 . 
 역주 : libUV 는 Node . js 에서 사용 하 는 비동기 I / O 라이브러리 이 다 . 이 라이브러리 는 C 로 작성 되 었 고 윈도우 나 리눅스 커널 을 추상 화 해서 Wrapping 하 고 있 는 구조 이 다 . 즉 , 커널 에서 어떤 비동기 작업 들 을 지원 해 주 는 지 알 고 있 기 때문 에 커널 을 사용 하 여 처리 할 수 있 는 비동기 작업 을 발견 하 면 바로 커널 로 작업 을 넘겨 버린다 . 
 이후 이 작업 들 이 종료 되 어 OS 커널 로부터 시스템 콜 을 받 으면 이벤트 루프 에 콜백 을 등록 하 는 것 이 다 . 만약 OS 커널 이 지원 하 지 않 는 작업 일 경우 별도 의 스레드 에 작업 을 던져서 처리 한다 . 이 스레드 에 관한 내용 은 원작자 가 밑 에서 추가 적 으로 설명 하 고 있 다 . 
 이벤트 루프 의 작업 흐름 
 그러나 만약 이벤트 루프 를 돌려야 할 상황 이 라면 Node . js 는 이벤트 루프 의 첫 번 째 페이즈 인 Timer phase 를 실행 한다 . 
 이벤트 루프 가 Timer phase 에 들어가 게 되 면 실행 할 타이머 콜백 큐 에 뭐 가 있 는 지 확인 부터 시작 한다 . 그냥 확인 이 라고 하 면 간단 해 보이 지만 사실 이벤트 루프 는 적절 한 콜백 들 을 찾 기 위해 몇 가지 단계 를 수행 하 게 된다 . 
 만약 조건 에 해당 된다면 이 타이머 의 콜백 을 실행 하 고 다음 타이머 를 확인 한다 . 만약 조건 에 해당 하 지 않 는 타이머 를 만난다면 , 탐색 을 바로 종료 하 고 다음 페이즈 로 이동 한다 . 타이머 는 힙 내부 에 오름차순 으로 정렬 되 어 있 기 때문 에 그 이후 로 는 탐색 을 해도 의미 가 없 기 때문 이 다 . 
 자 , 예 를 들 어 딜레이 값 이 100 , 200 , 300 , 400 인 4 개 의 타이머 ( A , B , C , D ) 를 어떤 특정 시간 t 에 힙 에 등록 했 다고 가정 해 보 자 . 
 이제 이벤트 루프 가 t + 250 에 Timer phase 에 진입 했 다고 생각 해 보 자 . 
 가장 먼저 타이머 A 를 찾아낸 후 만료 기간 이 t + 100 이 라는 것 을 알 게 될 것 이 다 . 그러나 지금 은 이미 t + 250 이 기 때문 에 타이머 A 의 콜백 은 실행 될 것 이 다 . 그리고 타이머 B 를 찾아내 어 만료 기간 이 t + 200 임 을 체크 하 게 되 고 , 타이머 B 의 콜백 역시 실행 된다 . 
 이제 타이머 C 를 체크 하 게 되 는데 이 타이머 의 딜레이 는 t + 300 이 기 때문 에 바로 페이즈 가 종료 된다 . 이벤트 루프 는 타이머 D 는 체크 하 지 않 는데 , 위 에서 설명 했 듯이 타이머 들 은 만료 기간 순 으로 오름차순 정렬 되 어 있 기 때문 에 타이머 C 의 뒤쪽 에 는 어차피 타이머 C 보다 만료 기간 이 긴 타이머 들 만 있 기 때문 이 다 . 
 참고 로 페이즈 는 시스템 의 실행 한도 에 도 영향 을 받 고 있 으므로 , 실행 되 어야 하 는 타이머 가 아직 남 아 있 다고 하 더라도 시스템 실행 한도 에 도달 한다면 바로 다음 페이즈 로 넘어가 게 된다 . 
 Pending i / o phase 
 타임 페이즈 가 종료 된 후 이벤트 루프 는 Pending i / o 페이즈 에 진입 하 고 , 가장 먼저 이전 작업 들 의 콜백 이 실행 대기 중 인지 , 즉 pending _ queue 에 들어와 있 는지 를 체크 하 게 된다 . 만약 실행 대기 중 이 라면 pending _ queue 가 비 거나 시스템 의 실행 한도 초과 에 도달 할 때 까지 대기 하 고 있 던 콜백 들 을 실행 한다 . 이 과정 이 종료 되 면 이벤트 루프 는 Idle Handler Phase 로 이동 하 게 된 후 내부 처리 를 위한 Prepare phase 를 거쳐 최종 적 으로 가장 중요 한 단계 인 Poll Phase 에 도달 하 게 된다 . 
 이름 에서 알 수 있 듯이 이 페이즈 는 폴링 하 는 단계 이 다 . 이벤트 루프 가 Poll phase 에 들어왔 을 때 watcher _ queue 내부 에 파일 읽 기 의 응답 콜백 , HTTP 응답 콜백 같이 수행 해야 할 작업 들 이 있 다면 이 작업 들 을 실행 하 게 된다 . 이 과정 또한 watcher _ queue 가 비 거나 시스템 의 실행 한도 초과 에 다다를 때 까지 계속 된다 . 
 만약 더 이상 콜백 들 을 실행 할 수 없 는 상태 가 된다면 만약 check _ queue , pending _ queue , closing _ callbacks _ queue 에 해야 할 작업 이 있 는지 를 검사 하 고 , 만약 해야 할 작업 이 있 다면 바로 Poll phase 가 종료 되 고 다음 페이즈 로 넘어가 게 된다 . 하지만 특별히 해야 할 작업 이 더 이상 없 는 경우 Poll phase 는 다음 페이즈 로 넘어가 지 않 고 계속 대기 하 게 된다 . 
 이때 도 마냥 무한 대기 를 하 는 것 은 아니 고 , 타이머 힙 에서 첫 번 째 타이머 를 꺼내 본 다음 에 만약 해당 타이머 가 실행 가능 한 상태 라면 그 타이머 의 딜레이 시간 만큼 만 대기 시간 을 결정 한다 . 
 역주 : 결국 Poll phase 에서 더 이상 수행 할 작업 이 없 는 경우 다음 페이즈 로 넘어가 는 조건 은 다음 과 같 다 . 
 Check phase 에 실행 할 콜백 이 있 냐 ? 바로 다음 페이즈 로 고고 
 Check phase 에 도 실행 할 콜백 이 없 어 ? 그럼 타이머 는 있 어 ? 있 다면 그 타이머 를 실행 할 수 있 는 시간 이 될 때 까지 만 대기 하 고 바로 Timer phase 로 고고 
 없 어 ? 그럼 일 생길 때 까지 대기 … 
 2 번 조건 에서 타이머 가 있 다고 바로 Timer phase 로 넘어가 지 않 고 대기 하 는 이유 는 , 대기 하 지 않 고 바로 Timer phase 로 넘어갈 경우 어차피 첫 번 째 타이머 를 실행 할 수 있 는 시간 이 안 되 었 기 때문 에 Timer phase 에서 도 아무것 도 수행 하 지 않 고 Timer phase 가 끝나 버리 기 때문 이 다 . 그렇게 되 면 굳이 이벤트 루프 를 한번 더 돌 아야지 해당 타이머 를 실행 할 수 있 게 되 므로 그냥 Poll phase 에서 대기 하 는 게 더 이득 인 것 이 다 . 
 Poll phase 가 지나 면 이벤트 루프 는 바로 setImmediate ( ) API 의 콜백 과 관련 이 있 는 Check phase 에 들어서 게 된다 . 이 페이즈 에서 는 다른 페이즈 와 마찬가지 로 큐 가 비 거나 시스템 실행 한도 초과 에 도달 할 때 까지 계속 해서 setImmediate 의 콜백 들 을 실행 한다 . 
 Check Phase 가 종료 된 후 에 , 이벤트 루프 의 다음 목적지 는 close 나 destory 콜백 타입 들 을 관리 하 는 Close callback 이 다 . 
 이벤트 루프 가 Close callback 들 과 함께 종료 되 고 나 면 이벤트 루프 는 다음 에 돌 아야 할 루프 가 있 는지 다시 체크 하 게 된다 . 만약 아니 라면 그대로 이벤트 루프 는 종료 된다 . 하지만 만약 더 수행 해야 할 작업 들 이 남 아 있 다면 이벤트 루프 는 다음 순회 를 돌 기 시작 하 고 다시 Timer Phase 부터 시작 하 게 된다 . 
 아까 위 에서 설명 했 던 Timer Phase 에서 의 예시 를 다시 가져오 자면 , 이제 다음 루프 의 Timer Phase 에서 는 타이머 C 의 만료 시간 이 경과 했 는지 부터 확인 을 시작 할 것 이 다 . 
 근데 이벤트 루프 의 일부 가 아닌 이 두 큐 에 들 어 있 는 콜백 들 은 언제 실행 되 는 걸까 ? 이 두 큐 의 콜백 들 은 어떤 페이즈 에서 다음 페이즈 로 넘어가 기 전 에 자신 이 가지 고 있 는 콜백 들 을 최대한 빨리 실행 해야 하 는 역할 을 맡 고 있 다 . ( 역주 : 페이즈 에서 다른 페이즈 로 넘어가 는 과정 을 Tick 이 라고 부른다 . ) 
 다른 페이즈 들 과 는 다르 게 이 두 큐 는 시스템 실행 한도 초과 에 영향 을 받 지 않 기 때문 에 Node . js 는 이 큐 가 완전히 비워 질 때 까지 콜백 들 을 실행 한다 . 그리고 nextTickQueue 는 microTaskQueue 보다 는 높 은 우선 순위 를 가지 고 있 다 . 
 필자 가 자바 스크립트 개발자 에게 가장 많이 들 은 단어 는 바로 스레드 풀 ( ThreadPoll ) 이 다 . 그리고 이 와 관련 된 가장 큰 오해 는 바로 Node . js 가 모든 비동기 명령 을 관리 하 는 별도 의 스레드 풀 을 가지 고 있 다는 것 이 다 . 그러나 이 스레드 풀 은 Node . js 가 아니 라 Node . js 가 비동기 작업 을 처리 하 기 위해 사용 하 는 라이브러리 인 libUV 에 포함 된 기능 이 라는 것 이 다 . 
 필자 가 이벤트 루프 의 다이어그램 에 스레드 풀 을 별도 로 표시 하 지 않 은 이유 는 스레드 풀 자체 가 이벤트 루프 매 커 니즘 의 일부 가 아니 기 때문 이 다 . libUV 는 OS 커널 의 비동기 API 만 을 사용 하 여 이벤트 드리븐 을 유도 할 수 있 을 만큼 충분히 훌륭 하 다 . 그러나 파일 읽 기 , DNS Lookup 등 OS 커널 이 비동기 API 를 지원 하 지 않 는 작업 들 의 경우 에 는 별도 의 스레드 풀 을 사용 하 게 되 는데 , 이때 기본 값 으로 4 개 의 스레드 를 사용 하 도록 설정 되 어 있 다 . uv _ threadpool 환경 변수 를 사용 하 면 최대 128 개 까지 스레드 개수 를 늘릴 수 도 있 다 . 
 이제 이 동기식 무한 루프 가 자바 스크립트 를 비 동기식 으로 작동 하 게 하 기 위해 얼마나 큰 역할 을 하 고 있 는 지 이해 했 기 바란다 . 이 구조 는 한 번 에 단 한 개 의 작업 만 실행 하 고 있 지만 그 어떤 것 도 블로킹 하 지 않 는다 . 어쨌든 백문 이 불여일견 이 니 , 코드 스니 펫 으로 이 구조 를 다시 이해 해 보 는 시간 을 가져 보 도록 하 자 . 
 Snippet 1 – 기초 이해 
 위 결과 를 예측 할 수 있 겠 는가 ? 아마도 여러분 은 setTimeout 이 먼저 출력 된다고 생각 하 겠 지만 , 사실 장담 할 수 없 다 . 
 왜 냐 ? 메인 모듈 이 실행 되 고 나 서 이벤트 루프 가 Timer phase 에 진입 할 때 여러분 의 타이머 를 찾 을 수 도 있 고 못 찾 을 수 도 있 기 때문 이 다 . 왜냐면 타이머 는 시스템 의 시간 과 사용자 가 제공 한 시간 을 사용 하 여 등록 되 기 때문 이 다 . setTimeout 이 호출 된 순간 , 타이머 는 메모리 에 이 타이머 를 저장 하 게 되 는데 , 그 순간 컴퓨터 의 성능 이나 Node . js 가 아닌 외부 작업 때문 에 약간 의 딜레이 가 발생 할 수 있 기 때문 이 다 . 
 또 다른 포인트 는 Node . js 가 Timer phase 에 진입 하 기 전 에 변수 now 를 선언 하 고 그 변수 now 를 현재 시간 으로 간주 한다는 점 이 다 . 그러므로 정확 한 계산 이 라고 하 기 에 는 약간 의 노이즈 가 껴 있 다는 것 이 고 , 이게 바로 setTimeout 이 반드시 먼저 실행 될 것 이 라고 확신 할 수 없 는 불 확실 성 의 이유 가 된다 . 
 그러나 만약 여러분 이 이 코드 를 I / O 사이클 의 내부 로 옮긴다면 , 반드시 setTimeout 보다 setImmediate 가 먼저 실행 되 는 것 을 보장 할 수 있 게 된다 . 
 역주 : 위 스니 펫 은 다음 과 같 은 순서 로 실행 된다 . 
 fs . readFile 을 만나 면 이벤트 루프 는 libUV 에게 해당 작업 을 던짐 . 
 파일 읽 기 는 OS 커널 에서 비동기 API 를 제공 하 지 않 기 때문 에 libUV 는 별도 의 스레드 에 해당 작업 을 던짐 
 작업 이 완료 되 면 이벤트 루프 는 Pending i / o callback phase 의 pending _ queue 에 작업 의 콜백 을 등록 
 이벤트 루프 가 Pending i / o callback phase 를 지날 때 해당 콜백 을 실행 
 setTimeout 이 Timer phase 의 큐 에 등록 됨 . 해당 콜백 은 다음 Timer phase 때 실행 될 것 이 다 . 
 setImmediate 의 콜백 이 Check phase 의 check _ queue 에 등록 됨 . 
 Poll phase 에서 는 딱히 할 일 이 없 지만 , Check phase 의 큐 에 작업 이 있 으므로 바로 Check phase 로 이동 
 setImmediate 콘솔 출력 . Timer phase 에 는 타이머 가 등록 되 어 있 으므로 다시 이벤트 루프 가 시작 된다 . 
 Timer phase 에서 타이머 를 검사 , 딜레이 가 0 이 므로 setTimeout 의 콜백 을 바로 실행 한다 . 
 setTimeout 콘솔 출력 
 즉 , 이런 과정 을 거치 기 때문 에 setImmediate 의 콜백 이 반드시 setTimeout 보다 먼저 실행 되 는 것 을 보장 할 수 있 는 것 이 다 . 
 Snippet 2 – 타이머 들 을 더 잘 이해 하 기 
 위 의 예시 는 굉장히 간단 하 다 . 함수 foo 는 setImmediate ( ) 에 의해서 1000 번 재귀 호출 되 고 있 다 . 이 코드 를 필자 의 맥북 프로 에서 Node . js 8 . 9 . 1 을 사용 하 여 실행 하 면 함수 가 종료 되 기 까지 6 ~ 8 ms 정도 가 걸린다 . 이제 위 의 스니 펫 을 setImmediate ( foo ) 에서 setTimeout ( foo , 0 ) 으로 변경 해 보 자 . 
 이제 필자 의 컴퓨터 에서 이 코드 가 실행 되 는 데 걸리 는 시간 은 대략 1400 + ms 정도 이 다 . 
 왜 이렇게 되 는 걸까 ? 이 두 경우 모두 폴링 에 걸리 는 시간 은 0 이 기 때문 에 동일 하 게 작동 해야 한다 . 근데 왜 이렇게 실행 시간 이 많이 차이 가 나 게 된 걸까 ? 
 그 이유 는 바로 시간 을 비교 하 고 편차 를 알아내 는 작업 이 CPU 를 집중 적 으로 사용 하 는 작업 이 기 때문 에 시간 이 더 오래 걸리 기 때문 이 다 . 그리고 타이머 스크립트 를 등록 하 는 것 자체 도 시간 을 소모 한다 . 위 에서 설명 했 듯이 Timer phase 에서 는 타이머 를 실행 할 시간 이 되 었 는지 , 콜백 을 실행 해야 하 는지 를 검사 하 기 위해 몇 가지 작업 을 거쳐야 하 는데 이 과정 을 매 이터레이션 마다 하 고 있 으니 느려질 수 밖 에 없 다 . 그러나 setImmediate 의 경우 이런 과정 이 필요 없 기 때문 에 setTimeout 보다 실행 속도 가 빠른 것 이 다 . 
 Snippet 3 – nextTick ( ) 과 타이머 실행 에 대해 이해 하 기 
 위 코드 의 로그 는 어떻게 출력 될까 ? 첫 번 째 출력 은 foo 들 이 될 것 이 고 기 후 setTimeout 들 이 출력 된다 . 제일 처음 코드 를 실행 하 면 2 ms 후 에 첫 번 째 foo 가 출력 되 고 다음 nextTickQueue 에 다시 foo ( ) 를 넣 게 된다 . 그리고 nextTickQueue 에 들어간 콜백 들 은 한 페이즈 에서 다음 페이즈 로 넘어갈 때 마다 무조건 콜백 들 을 동기 적 으로 실행 해야 한다 . 그렇 기 때문 에 재귀 호출 로 nextTickQueue 에 들어간 모든 콜백 들 을 실행 하 고 나 서 야 Timer Phase 에서 setTimeout 콜백 을 처리 할 수 있 게 되 는 것 이 다 . 
 코드 를 약간 수정 하 고 다시 살펴보 자 . 
 필자 는 기존 의 setTimeout 과 같 은 딜레이 시간 을 가진 setTimeout 을 추가 했 고 , 이 타이머 의 콜백 은 단지 Other setTimeout 을 출력 하 기 만 한다 . 뭐 , 확실 하 다고 말 할 수 는 없 지만 한 개 의 foo 가 먼저 출력 된 후 Other setTimeout 이 출력 될 찬스 가 존재 하 긴 한다 . 타이머 들 이 들 어 있 는 힙 내부 에 동일 한 딜레이 를 가진 타이머 들 은 어떻게 든 그룹 화 되 어 있 고 , nextTickQueue 의 체크 는 진행 중 인 콜백 그룹 의 실행 이 끝난 후 에 야 진행 되 기 때문 이 다 . 
 역주 : 첫 번 째 setTimeout 의 콜백 과 두 번 쨰 setTimeout 의 콜백 이 실행 될 시간 이 동일 하 기 때문 에 Timer phase 에서 두 타이머 의 콜백 이 모두 큐 에 들어갔 고 , 일단 큐 에 들어갔 으면 시스템 실행 한도 에 걸리 지 않 는 이상 무조건 해당 페이즈 가 끝 나기 전 에 실행 된다 . 
 이런 경우 , foo ( ) 내부 의 process . nextTick 의 콜백 은 Timer phase 에서 다음 페이즈 로 넘어갈 때 실행 될 것 이 기 때문 에 foo 가 먼저 한 개 출력 되 고 Other setTimeout 이 출력 되 는 것 이 다 . 
 몇 가지 일반 적 인 질문 들 
 그럼 자바 스크립트 는 정확히 어디 에서 실행 되 는 건가요 ? 
 이 포스트 를 처음 읽 을 때 는 자바 스크립트 가 정확히 어디 서 실행 되 는지 헷갈릴 수 있 다 . 
 앞서 말 했 듯이 이벤트 루프 자체 에서 V 8 또는 다른 엔진 을 사용 하 여 자바 스크립트 를 실행 하 는 것 이 고 이때 , 단 하나 의 스레드 를 사용 하 여 자바 스크립트 가 실행 되 는 것 이 다 . 실행 자체 는 동기 적 이 고 , 현재 실행 시킨 자바 스크립트 의 실행 이 완료 되 지 않 는다면 이벤트 루프 또한 진행 되 지 않 는다 . 
 왜 우리 는 setTimeout ( fn , 0 ) 가 있 는데도 setImmediate 를 사용 해야 하 나요 ? 
 일단 , setTimeout ( fn , 0 ) 은 실질 적 으로 0 이 아니 다 . 이건 사실 1 이 다 . 여러분 이 타이머 의 딜레이 를 1 ms 보다 작 거나 2147483647 ms 보다 크 게 설정 하 면 딜레이 는 자동 으로 1 로 세팅 된다 . 그렇 기 때문 에 setTimeout 의 딜레이 를 0 으로 설정 하 려고 한다면 이 딜레이 는 자동 으로 1 이 되 는 것 이 다 . 
 이미 설명 했 던 대로 setImmediate 을 사용 하 면 타이머 를 사용 할 때 처럼 딜레이 의 시간 이 지났 지 뭐 니 하 는 추가 적 인 체크 로직 이 필요 없 어 진다 . 그래서 setImmediate 은 빠른 것 이 다 . 또한 setImmediate 의 체크 작업 은 Poll phase 직후 에 수행 되 기 때문 에 HTTP 요청 콜백 과 같 은 곳 에서 사용 된 setImmediate 의 콜백 함수 는 바로 실행 된다고 할 수 있 다 . 
 왜 setImmediate 는 Immediate ( 즉시 ) 라고 부르 는 건가요 ? 
 setImmediate 와 process . nextTick 모두 이름 이 잘못 되 었 다고 생각 한다 . 사실 setImmediate 은 한 번 의 Tick 또는 루프 가 돌 때 마다 관리 되 고 , nextTick 은 한 페이즈 에서 다음 페이즈 로 넘어가 는 매 Tick 마다 최대한 빨리 호출 되 도록 작동 하 고 있 다 . 그래서 기능 적 으로 는 setImmediate 이 nextTick 이 라는 이름 이 더 잘 어울리 고 nextTick 이 진짜 로 즉시 ( Immediately ) 호출 되 는 친구 다 . 😛 
 자바 스크립트 는 Block 될 수 있 는 건가요 ? 
 이미 위 에서 설명 했 듯이 nextTickQueue 는 콜백 실행 에 있 어서 어떤 제한 도 가지 고 있 지 않 다 . 그렇 기 때문 에 만약 process . nextTick ( ) 이 재귀 호출 된다면 , 여러분 의 프로그램 은 다른 페이즈 들 의 큐 가 어떤 콜백 을 가지 고 있 든 간 에 절대 그 작업 에서 빠져 나오 지 못할 것 이 다 . 
 만약 Exit callback phase 에서 setTimeout 을 호출 하 면 어떻게 되 나요 ? 
 뭐 , 타이머 자체 는 시작 될 수 있 겠 지만 setTimeout 의 콜백 은 절대 호출 되 지 않 을 것 이 다 . 이미 Node . js 가 Exit callbacks 에 들어와 있 다는 것 은 이미 이벤트 루프 에서 빠져나왔 다는 것 을 의미 한다 . 
 짧 은 정리 들 
 이벤트 루프 는 작업 스택 을 가지 고 있 지 않 다 . 
 이벤트 루프 가 별도 의 스레드 에서 실행 되 고 자바 스크립트 실행 은 어떤 큐 에서 하나 씩 꺼내 와서 다른 곳 에서 하 는 것 이 아니 라 자바 스크립트 의 실행 자체 가 이벤트 루프 안 에서 수행 되 는 것 이 다 . 
 setImmediate 는 콜백 을 작업 큐 의 앞 쪽 에 밀 어 넣 는 것 이 아니 라 setImmediate 만 을 처리 하 기 위한 전용 페이즈 와 큐 가 존재 한다 . 
 setImmediate 은 실질 적 으로 다음 페이즈 혹은 다음 이벤트 루프 의 순회 에서 실행 되 고 , nextTick 이 오히려 실질 적 으로 더 빠르 게 실행 된다 . 
 nextTickQueue 에 담긴 작업 이 재귀 호출 을 수행 하 는 경우 Node . js 의 작업 프로세스 를 블록 킹 할 수 있 다 . 주의 하 도록 하 자 . 
 필자 는 Node . js 코어 개발 팀 이 아니 다 . 이 포스팅 에 관한 필자 의 모든 지식 은 실험 과 다른 포스팅 들 을 통해 얻 은 것 이 다 . 필자 가 이 지식 들 을 얻 기 위해 가장 큰 도움 을 준 Node . js 공식 문서 에 감사 한다 . 
 두 번 째 로 도움 이 많이 된 포스트 는 Saúl Ibarra Corretgé 의 talk on libUV 포스팅 이 다 . 
 그리고 세 번 째 로 VoidCanvas 독자 분 들 덕분 에 이런 건강 한 토론 과 실험 , 관찰 을 할 수 있 도록 된 것 에 감사 한다 . 🙂 
 Event LoopJavaScriptNodeJS 이벤트 루프 
 1 왜 이 포스팅 을 작성 하 게 되 었 나 ? 
 2 대표 적 인 잘못 된 개념 들 
 2 . 1 이벤트 루프 는 자바 스크립트 엔진 내부 에 있 다 
 2 . 2 이벤트 루프 는 하나 의 스택 또는 하나 의 큐 로 만 작동 한다 
 2 . 3 이벤트 루프 는 여러 개 의 스레드 에서 실행 된다 
 2 . 4 setTimeout 은 일부 비동기 OS API 와 관련 있 다 . 
 2 . 5 setImmediate 의 콜백 은 작업 큐 의 가장 첫 번 째 에 위치 한다 
 3 이벤트 루프 의 구조 
 3 . 1 Timer phase 
 3 . 2 Pending i / o callback phase 
 3 . 3 Idle , Prepare phase 
 3 . 4 Poll phase 
 3 . 5 Check phase 
 3 . 6 Close callbacks 
 3 . 7 nextTickQueue 와 microTaskQueue 
 4 이벤트 루프 의 작업 흐름 
 4 . 1 Timer phase 
 4 . 2 Pending i / o phase 
 4 . 3 Poll phase 
 4 . 4 Check phase 
 4 . 5 Close callback 
 4 . 6 nextTickQueue & microTaskQueue 
 4 . 7 Thread - pool 
 5 Workflow with examples 
 5 . 1 Snippet 1 – 기초 이해 
 5 . 2 Snippet 2 – 타이머 들 을 더 잘 이해 하 기 
 5 . 3 Snippet 3 – nextTick ( ) 과 타이머 실행 에 대해 이해 하 기 
 6 몇 가지 일반 적 인 질문 들 
 6 . 1 그럼 자바 스크립트 는 정확히 어디 에서 실행 되 는 건가요 ? 
 6 . 2 왜 우리 는 setTimeout ( fn , 0 ) 가 있 는데도 setImmediate 를 사용 해야 하 나요 ? 
 6 . 3 왜 setImmediate 는 Immediate ( 즉시 ) 라고 부르 는 건가요 ? 
 6 . 4 자바 스크립트 는 Block 될 수 있 는 건가요 ? 
 6 . 5 만약 Exit callback phase 에서 setTimeout 을 호출 하 면 어떻게 되 나요 ? 
 7 짧 은 정리 들