What the heck is the event loop anyway? | Philip Roberts | JSConf EU - hochan222/Everything-in-JavaScript GitHub Wiki
How does JavaScript even work?
v8?? chrome runtime??? 들어는 봤지만 자세히는 모른다.
그리고 또 물어본다.
What is Javascript?
A single-threaded, non-blocking, asynchronous, concurrent language. It have call stack, an event loop, a callback queue some other apis and stuff.
라고 수도 없이 들어봤지만 여전히 그 의미를 정확히 모른다.
V8을 clone해서 찾아보았더니 setTimeout 이나 DOM, HTTP Request를 관리하는 코드들을 찾아 볼 수 없었다.
다음은 간략화된 Javascript Runtime 구조이다.
자바스크립트는 싱글 쓰레드 프로그래밍 언어이다. 싱글 쓰레드 런타임을 가지고 있다는 뜻인데, 결국 한번에 하나의 싱글 콜 스택만 가질 수 있음을 의미한다. (== 하나의 프로그램은 동시에 하나의 코드만 실행 할 수 있다.)
Call Stack은 자료구조로 실행되는 순서를 기억하고 있다. 그리고 스택의 가장 위쪽에서 해당 함수를 꺼내게 되는데 이게 콜 스택이 하는 일의 전부이다.
이 코드를 실행하면
- 실행되는 코드 자체를 의미하는 main 함수를 스택에 집어 넣게 된다.
- 함수를 정의하게 된다.
- 마지막에 printSquare(4)를 만나고 함수 호출이니, 스택에 함수를 추가한다.
- 호출되는 함수들을 stack에 넣고 무엇인가 return 할 때마다 스택 맨위에있는것을 꺼내게 된다.
- printSquare는 return이 없지만 암묵적으로 return 한다.
만약 위의 경우 에러가 난다면 크롬 개발자 도구에서 스택의 꼬리를 물면서 Oops!를 표시할 것이다. 점점 올라가다가 익명함수(=main함수)까지 올라간다!
blocking
블로킹의 정확한 정의는 존재하지 않는다. 그저 느리게 동작하는 코드 일 뿐이다.
console.log는 느리지 않지만 여러번 while문으로 호출되면 느릴것이다.
네트워크 요청이나 이미지 프로세싱은 느리다. 느린 동작이 스택에 남아있는 것을 보통 블로킹이라고 한다.
var foo = $.getSync('//foo.com');
var bar = $.getSync('//bar.com');
var qux = $.getSync('//qux.com');
console.log(foo);
console.log(bar);
console.log(qux);
위 코드와 alert버튼하고 밑에 a href 태그가 있다고 치자.
우리는 alert버튼을 눌러도 위 요청 응답이 끝날 때 까지 alert 메세지 창이 뜨지 않을것이다.
또한, a href 태그 또한 동작하지 않는다.
브라우저는 모든 리퀘스트가 완료될 때 까지 멈춰있을 것이다.
멈춰있는동안 행동을 기억하고 있지만 렌더링 할 수 없다.
the solution?
asynchronous callbacks
브라우저나 node.js에는 블록킹함수가 거의 없다. 대부분 비동기로 만들어졌다. 어떤 코드를 실행하면 결국 콜백을 받고 나중에 실행하는것을 의미한다.
settimeout함수를 실행시킬 때 Call Stack에는 추가가 되지만 사라졌다가 설정한 시간 이후에 다시 실행 될 것이다.
Concurrency & the Event Loop
이벤트 루프와 동시성이 이 역할을 한다.
자바스크립트는 동시에 한가지 일 밖에 하지 못한다. 하지만 우리가 이 여러 일들을 동시에 할 수 있는 이유는 브라우저는 단순 런타임 이상을 의미하기 때문이다.
Call Stack에서 setTimeout을 webapis에 추가시킨 후 pop한다.
webapis는 바로 Call Stack에 추가할 수 없기 때문에 task queue에 집어넣어진다.
이벤트 루프에 다달았다.
이벤트 루프는 이 전체 시스템에서 아주 단순한 일을 하는 작은 파트이다.
이벤트 루프의 역할은 콜 스택과 테스크 큐를 주시하는 것이다.
스택이 비어있으면, 큐의 첫번째 콜백을 스택에 쌓아 효과적으로 실행 할 수 있게 해준다.
스택이 비어있을때 이벤트 루프는 콜백을 스택에 넣어준다.
Stack은 자바스크립트 영역이다. V8엔진에서 이제 console.log("there")을 실행한다.
// Synchronous
[1, 2, 3, 4].forEach(function (i) {
console.log('processing sync')
delay();
});
// Asynchronous
function asyncForEach(array, cb) {
array.forEach(function () {
setTimeout(cb, 0);
})
}
asyncForEach([1, 2, 3, 4], function (i) {
console.log('processing async', i);
delay();
})
두가지 함수에 차이는 동기는 Stack영역에서만 실행된다는 것이고 비동기는 Task Queue에 쌓였다가 Stack영역이 비워지면 실행된다는것이다.
따라서, 아주 큰 배열 작업을 할 때, 동기로 처리하게 되면 다른 작업을 진행하지 못할 수 있어서 비동기로 처리하는것이 이상적이다.
렌더 또한 마찬가지인데 렌더링도 스택이 비워질 때 까지 기다렸다가 실행된다.
다른 점이라면 렌더는 콜백에 비해 더 높은 우선순위를 갖는다.
스택에 느린 작업을 넣어서 브라우저가 할일을 못하게 하지 말아라, 유동적인 UI를 만들어라.
그 예시로 스크롤 이벤트롤 등록할 때, 엄청난 이벤트들이 Callback Queue에 쌓이게 될것이다.
우리는 일정 초의 텀을 두고 실행하므로써 이벤트 범람을 방지 할 수 있다.