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은 자료구조로 실행되는 순서를 기억하고 있다. 그리고 스택의 가장 위쪽에서 해당 함수를 꺼내게 되는데 이게 콜 스택이 하는 일의 전부이다.

이 코드를 실행하면

  1. 실행되는 코드 자체를 의미하는 main 함수를 스택에 집어 넣게 된다.
  2. 함수를 정의하게 된다.
  3. 마지막에 printSquare(4)를 만나고 함수 호출이니, 스택에 함수를 추가한다.
  1. 호출되는 함수들을 stack에 넣고 무엇인가 return 할 때마다 스택 맨위에있는것을 꺼내게 된다.
  2. 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에 쌓이게 될것이다.
우리는 일정 초의 텀을 두고 실행하므로써 이벤트 범람을 방지 할 수 있다.