Java Script 는 코드 실행, 이벤트 수집과 처리, 큐에 놓인 하위 작업들을 담당하는 이벤트 루프에 기반한 동시성(concurrency) 모델을 가지고 있습니다. 이 모델은 C 또는 Java와 같은 언어와 완전히 다릅니다.
- developer.mozilla.org
Java Script 를 공부하고, 사용하다 보면 그 특유의 유연함 덕분에 이해하기 힘든 일들이 벌어 지거나, 손에 익어 사용 할 줄은 알지만, 어떻게 동작하는지 머릿속으로 그리기 힘든 상황이 많이 생긴다.
그중 가장 이해하기 힘들었던것이 콜스택, 그리고 비동기 작업이 어떻게 처리되는가 였는데, 이 글을 통해 지금까지의 약소한 깨우침을 정리해보려 한다.
Java Script 는 싱글 쓰레드(single-threaded) 언어이다. 즉 콜스택이 하나이기에 한 번에 한가지의 작업만을 수행할 수 있다.
Stack, Heap, Queue, Event-Loop 등등 Java Script 동작 원리에 대해 관심이 있다면, 한번쯤은 들어 보았을 단어 들이다. 이 글의 핵심인 비동기 작업 처리에 대해 이해하려면 우선 Java Script 의 동작 원리에 대해 전반적으로 알고있어야한다.
위 사진을 보면 메모리 할당이 일어나는 힙과, 콜스택이 보이는데 콜스택은 기본적으로 함수의 호출을 기록하고, 호출 순서를 기억하는 자료구조이다. 즉 코드가 작동되면서 함수가 호출되면, 해당 함수는 스택의 가장 위에 놓이게되고, 만약 그 함수가 다른 함수를 호출 한다면 또 그위에 쌓이게된다.
그리고 각 함수가 실행 후 리턴 될때는 실행 순으로, 즉 콜스택의 가장 윗 부분 차례로 지워지는것이다.
위 그림은 이벤트 루프를 가장 잘 표현한 그림이라고 생각한다. 왼쪽 상단엔 위에서 언급한 Heap과 Stack이, 그리고 오른쪽 상단엔 브라우저가 제공하는 DOM, Ajax, setTimeout 와 같은 웹 API가 보인다.
브라우저측에서 제공하는 웹 API가 있기에, 한번의 하나의 코드만 실행할 수 있는 싱글 쓰레드 언어인 자바스크립트는 동시성을 가질 수 있다.
Loupe(http://latentflip.com) 는 브라우저에서 자바스크립트가 어떻게 동작하는지 가장 쉽고 직관적으로 보여주는 사이트다. 접속하게되면 샘플 코드가 타이핑 되어 있는데, 샘플 코드를 통해 자세히 알아보자.
위 코드를 실행시키면 어떤 결과가 출력될지 예측해보자. 5번 라인 까지는 이벤트 호출 메소드이니 특정 버튼을 누르지 않는 이상 실행되지 않을것이며, 콘솔창엔 7번 라인의 "Hi!"가 출력되고, 13번라인의 "Welcome to loupe."가 출력된 후 대략 5초 쯤 후에 "Click the button!"이 출력 될것이다.
어찌보면 당연한 결과이고, 익숙한 결과이다. 하지만 동작 원리상 어떻게 이런 결과가 나타나는 것일까?
먼저 코드 가 실행되면 5번 라인 까지의 함수가 호출되고, 콜스택에 쌓이게 된다.
처음 콜 스택에 올라온 함수가 인자로 갖고있는 콜백 함수는 당연히 콜백 실행 조건인 'click'을 하지 않았기에 작동되진 않았겠지만, 호출 자체는 끝났기때문에 바로 콜 스택에서 지워지게 된다. 그러나 비동기 메서드 이기에, 웹 API로 넘어가게 된다.
그 후 console.log("Hi!")가 호출되어 콜 스택에 쌓인 후 콘솔창엔 "Hi!"가 출력되고, 콜스택에서 지워지고, 다음 차례로 호출된 setTimeout 역시 호출 후 지워진다. 다만 비동기 메서드 이기에 위의 이벤트 메서드 처럼 웹 API로 넘어가게된다.
그러나 첫번째로 넘어간 '$.on~~' 이벤트 메서드와는 달리 즉시 실행 조건 없이 5초 후에 실행되게끔 되어있기에, 웹 API 단에서 5초의 카운트 다운이 진행 된 후 다음 단계인 콜백 큐로 넘어가게된다.
먼저 콜백 큐에 대해 간략히 설명하자면, 콜백 함수들이 대기하는 큐(FIFO) 형태의 배열이다.
자, 그럼 이제 어떤 일이 생길까?
콜백 큐에 있던 timeout()이 콜 스택으로 올라간것이 보인다. 여기서 우린 이벤트 루프가 어떤 기능을 수행하는지 알수있는데,
이벤트 루프는 콜 스택과 콜백 큐를 주시하는 것이다. 콜스택에 쌓여있던 함수가 리턴 후 스택이 비게되면 이벤트 루프는 콜백 큐에 대기중인 콜백을 콜스택에 쌓아주는 역활을 하는것이다.
console.log('first');
setTimeout(()=>{
console.log('second');
},0);
console.log('third');
//first
//third
//second
이제 이벤트 루프가 이해됬다면, 두개의 console.log 사이의 위치한 setTimeout의 지연시간이 0초로 설정 되어있음에도 불구하고, 콘솔창에 'first -> third -> second' 순으로 출력되는지 이해할 수 있을것이다 🥳