자바스크립트는 싱글 스레드
기반에 언어입니다. 이는 한번에 하나의 작업만을 처리할 수 있다는 것을 의미합니다. 하지만 우리가 자바스크립트를 사용해보면 동시에 여러 작업을 처리하는 것으로 보입니다. 자바스크립트는 동시성을 지원하기 위해서 이벤트 루프를 사용합니다. 이벤트 루프는 자바스크립트를 실행시켜주는 브라우저
에서 지원합니다.
먼저 브라우저에서 실행되는 자바스크립트의 구조는 아래 이미지처럼 구성되어있습니다.
function foo() {
console.log('foo');
}
function bar() {
console.log('bar');
}
setTimeout(foo, 0);
bar();
setTimeout
함수가 실행된다. 이때 setTimeout
함수가 콜 스택에 푸시되고 실행됩니다. 이때 Web API인 타이머 함수도 함수이므로 콜 스택에 푸시되어 실행됩니다. 그리고 콜백 함수 foo
를 예약하고 종료되어 콜 스택에서 팝합니다. 브라우저는 타이머가 종료되면 예약된 콜백 함수를 태스크 큐에 푸시합니다.bar
함수가 실행되어 콜 스택에 푸시되고 종료되면 콜 스택에서 팝됩니다. 여기까지 실행되면 콜 스택은 비어있게 됩니다.브라우저
에서 타이머가 종료되어 예약된 foo
함수를 태스트 큐에 푸시했다면, 이벤트 루프는 foo
함수를 콜 스택으로 이동시킵니다. 콜 스택에 푸시된 foo
함수는 실행되고 종료되어 콜 스택에서 팝됩니다.자바스크립에서 비동기 함수가 실행되면 태스크 큐에서 대기하다가 이벤트 루프에 의해서 콜 스택에 푸시되어 실행됩니다. 여기서 자바스크립트는 싱글 스레드 기반으로 동작하고 브라우저는 멀티 스레드로 동작하다는 것에 주의해야합니다. 만약 이러한 이벤트 루프가 없다면 자바스크립트가 실행 중인 코드가 완료되기 전까지는 사용자는 코드의 실행을 그대로 기다리고 있어야합니다.
자바스크립트에서는 비동기 함수의 실행 순서에 따라서 동기적으로 코드를 실행하기 위한 패턴으로 콜백 함수를 사용했습니다. 하지만 콜백 함수를 사용하는 패턴은 콜백 헬로 인해서 가독성과 에러 처리를 어렵게 만들었습니다.
ES6에서는 비동기 처리의 다른 방안으로 Promise
가 도입되었습니다. Promise
는 전통적인 콜백 헬을 해결하고 실행 순서를 명확하게 파악할 수 있는 장점이 있습니다. 또한 ES8에서는 async
/await
도 추가되었습니다.
setTimeout(() => console.log(1), 0);
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
Promise
의 후속 처리 메소드인 .then
,.catch
,.finally
또한 비동기로 작동합니다. 따라서 콘솔에 출력되는 순서는 1 → 2 → 3 으로 생각할 수 있지만 실제 출력은 2 → 3 → 1 순서로 출력됩니다.
그 이유는 Promise
의 후속처리 메소드는 태스크 큐
에서 처리되지 않고 마이크로 태스크 큐
에서 처리됩니다. 마이크로 태스트 큐는 태스크 큐보다 우선 순위가 높습니다. 그래서 태스트 큐에서 처리되는 setTimeout
보다 먼저 실행됩니다. 위 코드에서 이벤트 루프는 다음과 같이 동작합니다.