이벤트루프에는 6개의 단계가 있습니다. 각단계는 단계마다 처리해야하는 콜백함수를 담기위한 큐를 가지고 있습니다.
node main.js 명령어로 Node.js 애플리케이션을 콘솔에서 실행하면 Node.js는 먼저 이벤트 루프를 생성한 다음 메인 모듈인 main.js를 실행합니다. 이 과정에서 생성된 콜백들이 각 단계에 존재하는 큐에 들어가게 되는데 메인 모듈의 실행을 완료한 다음 이벤트 루프를 계속 실행할 지 결정합니다. 만약 큐가 모두 비어서 더 이상 수행할 작업이 없다면 Node.js는 루프를 빠져나가고 프로세스를 종료합니다.
이벤트루프는 time단계에서 시작합니다. 이 단계의 큐에는 setTimeout이나 setInterval과 같은 함수를 통해 만들어진 타이머들을 큐에 넣고 실행합니다. now - registeredTime ≥ delta 인 값을 가지는 타이머들이 큐에 들어갑니다
타이머들은 최소 힙1으로 관리됩니다. 힙을 구성할 때 기준으로 실행할 시각이 가장 적게 남은 타이머가 힙의 루트가 됩니다. Timer 단계에서 최소 힙에 들어 있는 타이머들을 순차적으로 찾아 실행한 후 힙을 재구성합니다.
이 단계의 큐에 들어있는 콜백들은 현재 돌고 있는 루프 이전의 작업에서 큐에 들어온 콜백입니다. 예를 들어 TCP 핸들러 내에서 비동기의 쓰기 작업을 한다면, TCP 통신과 쓰기 작업이 끝난 후 해당 작업의 콜백이 큐에 들어옵니다. 또 에러 핸들러 콜백도 pending_queue로 들어오게 됩니다.
Timer 단계를 거쳐 pending 콜백 단계에 들어오면 이전 작업들의 콜백이 pending_queue에서 대기중인지를 검사합니다. 만약 실행 대기 중이라면 시스템 실행 한도에 도달할 때까지 꺼내어 실행합니다.
Idle 단계는 매 틱(Tick, 매 단계가 이동하는 것을 의미함)마다 실행됩니다. Prepare 단계는 매 폴링마다 그 전에 실행됩니다. 이 두 단계는 Node.js의 내부 동작을 위한 것입니다.
이벤트 루프 중 가장 중요한 단계입니다. Poll 단계에서는 새로운 I/O 이벤트를 가져와서 관련 콜백을 수행합니다. 예를 들어 소켓 연결과 같은 새로운 커넥션을 맺거나 파일 읽기와 같이 데이터 처리를 받아들이게 됩니다. 이 단계가 가지고 있는 큐는 watch_queue입니다. 이 단계에 진입한 후 watch_queue가 비어 있지 않다면 큐가 비거나 시스템 실행 한도에 다다를 때까지 동기적으로 모든 콜백을 실행합니다. 만약 큐가 비게되면 Node.js는 곧바로 다음 단계로 이동하지 않고 check_queue(Check 단계의 큐), pending_queue(Pending 콜백 단계의 큐), closing_callbacks_queue(Close 콜백 단계의 큐)에 남은 작업이 있는지 검사한 다음 작업 있다면 다음 단계로 이동합니다. 만약 큐가 모두 비어서 해야할 작업이 없다면 잠시 대기를 하게 됩니다. 이때 대기시간은 타이머 최소 힙의 첫번째 타이머를 꺼내어 지금 실행할 수 있는 상태라면 그 시간만큼 대기한 후 다음 단계로 이동합니다. 이렇게 하는 이유는 바로 타이머 단계로 넘어간다고 해도 어차피 첫번째 타이머를 수행할 시간이 되지 않았기 때문에 이벤트 루프를 한 번 더 돌아야 하므로 Poll 단계에서 시간을 보내는 것입니다.
Check 단계는 setImmediate의 콜백만을 위한 단계입니다. 역시 큐가 비거나 시스템 실행 한도에 도달할 때 까지 콜백을 수행합니다.
socket.on('close', () => {})과 같은 close나 destroy 이벤트 타입의 콜백이 여기서 처리됩니다. 이벤트 루프는 Close 콜백 단계를 마치고 나면 다음 루프에서 처리해야 하는 작업이 남아 있는지 검사합니다. 만약 작업이 남아 있다면 Timer 단계부터 한 번 더 루프를 돌게 되고 아니라면 루프를 종료합니다.