자바스크립트와 이벤트 루프 : NHN Cloud Meetup
[JS] 도대체 이벤트 루프가 뭔가요?
[10분 테코톡] 🍗 피터의 이벤트루프
Javascript의 가장 큰 특징 중 하나는 Single Thread 기반 언어라는 점이다.
이말은 곧 동시에 한가지 작업만 처리 가능하다는 말이다.
아니 그러면 브라우저상에서 동시에 여러 작업이 처리되는건 뭘까?
예를 들면, 애니메이션 효과를 주면서 마우스 입력을 받아 처리하는 일 말이다.
이렇게 동시성(concurrency)을 제공하는 핵심요소는 바로 JS 엔진이 아니라 이벤트루프에 있다.
(Node JS는 libuv 라이브러리)
V8같은 Javascript 엔진은 단일 호출 스택(call Stack)을 사용하며, 요청이 들어올 때 마다 그 요청들을 스택에 담아 순차적으로 처리힌다.
여기서 요청이 들어온다는 것은 함수가 호출될 때 실행 컨텍스트가 쌓인다는 말이다.
그렇다면 비동기요청은 어떻게 이루어지며, 동시성에 대한 처리는 어떻게 되는걸까?
한번에 하나씩 처리된다는말은 함수가 하나 쌓일 때 마다 그 함수를 처리하고 다음 함수를 처리한다는 말일텐데 말이다.
또한 애니메이션 처리를 하고나서야 다음 이벤트를 처리할테고 말이다.
그 처리는 바로 자바스크립트 엔진을 구동하는 환경, 즉 브라우저나 Node.js가 담당한다.
엄밀히 말하면 브라우저가 제공하는 이벤트루프를 이용하여 처리한다.
브라우저의 구조는 아레와 같다.
setTimeTout, DOM 메서드, HTTP 요청같은 것들은 전부 자바스크립트 엔진이 아니라 브라우저에서 제공하는 Web API에서 제공하는 메소드들이다.
Web API 메소드들은 모두 비동기 메소드이며 작업을 마치면 콜백함수를 콜백큐(테스크 큐)에 집어 넣는다.
이렇듯 자바스크립트 엔진 자체는 싱글스레드 이지만 실제 자바스크립트가 구동되는 환경인 웹 브라우저에는 여러개의 스레드가 사용된다.
정확히 말하면 WebAPI가 멀티스레드로 동작하는것이다.
그리고 자바스크립트 엔진이 이 WebAPI 와 상호 연동을 하기 위해서 필요한 장치가 콜백큐(테스크 큐) 와 이벤트 루프이다.
중간에 setTimeout이라는 함수가 있고 그 첫번째 인자로 들어가있는 함수를 콜백함수라고 한다.
(특정 함수의 인자로 들어가는 함수)
콜백 함수는 콜백 수신 함수에 의해서 특정시점에 실행되며 그 특징인 아래와 같다.
비동기 콜백 예시
두번째 예시는
‘집에 잘 들어가셨나요’
‘노숙하시는 건가요?’
‘저기요…??’
의 순서로 출력된다.
자바스크립트 엔진은 코드를 진행하다가 중간에 setTimeout 같은 비동기 코드를 만나면 이 코드를 자바스크립트의 뒷편에서 실행해준다.
그 다음에 나머지 코드를 바로 진행하고 이 비동기 코드는 자바스크립트 뒤편에서 실행이 되도록 놔두는거다.
이벤트 루프는 바로 이 자바스크립트 코드가 실행되는 자바스크립트 엔진의 뒤편에서 일어나는 어떤 문맥의 일부로써 동작하는 하나의 장치라고 보면 된다.
아니 그런데 일단 자바스크립트 엔진이 무엇인가?
자바스크립트 엔진이란 webkit(사파리), V8(크롬) 같이 자바스크립트 코드를 해석하고 실행하는 인터프리터이다.
자바스크립트 엔진은 크게 힙과 콜스택으로 구분된다.
메모리 할당이 일어나는 부분이다.
변수나 객체들이 저장되는 창고정도로 생각하면 된다.
이벤트루프와는 크게 관련이 없으니 이정도만
자 여기서 setTimeout 함수가 실행되면 자바스크립트의 뒤편 WebAPI 에서 타이머가 작동한다.
타이머를 작동시키고 setTimeout은 자기 역할을 끝냈기 때문에 return 한다.
그리고 second 함수는 setTimeout함수밖에 없었기 때문에 second 함수도 return 된다.
fisrt 함수 까지 모두 return 된 후 타이머가 지정된 시간을 모두 카운트 하면 콜백함수를 큐로 보낸 후 콜스택이 이어있을 때 콜백함수가 콜스택에 쌓이고 실행된다.
setTimeout 함수를 사용했으니 setTimeout 함수의 콜백함수를 인자로가지고있는 타이머를 WebAPI에서 생성한다.
이 타이머를 생성하는거로 setTimeout의 역할은 끝이나고 setTimeout은 return 하고 물러난다.
setTimeout이 return 되었으니 마찬가지로 second함수로 return 되고 콜스택에서 사라진다.
firsrt 함수 내부의 콘솔들도 전부 출력되었으니 first함수도 return 되고 콜스택에서 사라진다.
이제 WebAPI에서 타이머만 남은 상태인데 타이머가 정해진 시간을 모두 채우고 나면
콜백함수를 콜백큐(테스크 큐)에 넣고 사라지게 된다.
그러면 콜백함수는 콜백큐에서 실행되기를 기다린다.
콜백함수도 결국 함수다.
함수는 실행되려면 결국 콜스택으로 들어가야 한다.
이때 콜스택이 비어있다면 콜백큐의 콜백함수는 콜스택으로 들어가고 실행된다.
이때 이벤트 루프 가 동작을 한다.
버튼의 클릭 이벤트 리스너가 등록되어있는 상태 에서 클릭 이벤트가 발생하면
콜백함수 A가 콜백 큐로 들어간다.
호출스택이 비어있으니 A는 콜스택으로 이동 후 A내부의 getJSON 메소드 실행
xmlHttpRequest가 콜백함수와 함께 WebAPI에서 생성
getJSON 메소드는 HTTP 리퀘스트를 보낸것으로 할일을 마쳤으니 return되고 사라짐
마찬가지로 A 함수도 내부에 getJSON 메소드만 있었으니 사라짐
getJSON 자기 할일 마치고 사라지면 A도 return 되고 사라짐
이제 WebAPI에 HTTP request 객체가 남은 상황이다.
이놈도 HTTP 요청을 보내고 받으면 사라지고 받은 응답을 인자에 담아 콜백함수를 콜백큐로 보낸다.
그리고 콜스택이 비어있으니 콜백함수 B는 이벤트루프에 의해 바로 콜스택으로 올라간다.
여기서 보면 B가 콜스택으로 올라갔을 땐 A가 이미 실행을 마치고 return 된 상태이다.
즉, A 와 B는 완전히 다른 문맥(context) 속에서 각각 동작을 하고 있는것이다.
그러니 A내부의 try/catch가 B의 에러를 잡지 못하는 것이다.
만일 B 내부의 에러를 잡고싶다면 그냥 아래처럼 B내부에 try/catch 해주면 된다.