비동기 처리(Asynchronous Processing)
하나의 작업의 종료까지 기다리지 않고 다음 작업을 진행하는 비순차적인 처리 방식
자바스크립트는 싱글 스레드 기반의 언어이기 때문에 작업을 처리하는 공간(호출 스택)이 하나이다.
즉, 한번에 하나의 작업만 수행할 수 있다. 따라서 비동기적으로 실행되는 setTimeout, addEventListener 등 포함된 로직은 작성한 순서대로 실행되지 않는다.
아래 예제를 보자
console.log('AAAA');
setTimeout(function(){
console.log('BBBB');
}, 0);
console.log('CCCC');
위의 예상 결과는 1. AAAA 2. BBBB 3.CCCC 라고 생각했지만 그렇지 않았다.
이유는 자바스크립트 엔진의 이벤트 루프(Event Loop) 및 콜백 큐(Callback Queue)의 역할로 인해 순서가 바꼈다.
자바스크립트 엔진(JavaScript Engine)
자바스크립트는 싱글 스레드 기반 언어이지만 사용자에게 동시에 여러 테스크가 처리되는 것처럼 느끼는것을 가능하게
해주는 것이 바로 이벤트 루프이다.
자바스크립트 엔진은 콜 스택(Call Stack)과 메모리 힙(Memory Heap) 으로 구성되어 있다.
또한 콜백 큐(Callback Queue)라는게 존재하는데 비동기적으로 실행되는 setTimeout, setInterval, 콜백 함수,
이벤트 핸들러가 일시적으로 보관되는 영역이다.
1. 콜 스택(Call Stack)
콜 스택 또는 호출 스택이라고 부르며, 스택이기 때문에 후입 선출(LIFO, Last-In-First-Out) 구조를 갖는다.
함수를 실행하면 해당 함수는 호출 스택의 가장 상단에 위치하게 되고 실행 후 해당 함수는 호출 스택에서 사라지게 됩니다.
아래 예시를 확인합니다.
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
위 코드는 정사각형의 넓이를 구하는 예제입니다. 함수 2개가 존재하며 printSquare 라는 함수를 실행하는 구문으로 자바스크립트는 시작됩니다.
Step 1. printSquare(5) 라는 함수 호출로 인해 콜스택에 처음으로 함수가 들어가게 됩니다.
Step 2. printSquare() 함수안에 multiply(x, x) 라는 함수 호출로 인해 가장 상단에 들어가게 됩니다.
Step 3. 더이상 처리할 함수가 존재하지 않으므로 가장 상단의 multiply(x,x) 함수를 처리하고 console.log() 를 처리한다.
Step 4. printSquare() 함수 처리한다.
Step 5. 비어있다.
위처럼 호출 스택의 각 단계를 스택 프레임(Stack Frame) 이라고 합니다.
만약 호출 스택의 크기를 초과하게 된다면 어떻게 되는지 확인해보자
아래와 같이 test() 함수는 자기 자신인 test() 함수를 호출하게 된다.
여기서 test(); 함수를 실행하게 되면 무한반복으로 자기자신인 test() 함수를 호출하게 되어 호출스택에 계속해서
쌓이게 된다.
아래의 이미지처럼 호출 스택에 계속해서 쌓이다보면 스택 오버플로우(Stack OverFlow) 가 발생하게 됩니다.
* 스택 오버플로우(Stack Overflow) : 정해진 스택 사이즈를 초과하게 되면 발생하는 에러
이처럼 자바스크립트는 하나의 호출 스택(Call Stack) 을 가지고 코드를 순차적으로 하나씩 처리하기 때문에
단일 스레드이며 동기식 언어라고 할 수 있다. 그래서 자바스크립트는 무한 루프는 발생할 수 있어도
동기화 문제인 교착상태(DeadLock)는 발생할 수 없습니다.
이러한 호출 스택에서 오래 걸리는 함수를 실행할 땐 해당 브라우저는 다음 작업을 진행하지 못하고
대기 상태가 된다. 예를 들어 우리가 자주 사용하는 네이버에서 검색을 했을 때 해당 페이지가 검색 데이터를
보여주지 않고 로딩화면에서 멈춰버리는것과 같습니다.
이러한 단일 스레드 방식인 자바스크립트로 기능이 많은 대형 사이트를 감당하기 위해서 필요한게
이벤트 루프 & 콜백 큐 입니다.
이벤트 루프 & 콜백 큐로 인해 단일 스레드지만 멀티 스레드처럼 느끼게 해준다.
원문 : https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf
2. 이벤트 루프(Event Loop)
비동기적으로 실행되는 setTimeout, setInterval, 콜백 함수, 이벤트 핸들러는 호출 스택에서 Web API에서
처리하도록 보내고 Web API에서 처리된 로직들은 콜백 큐(Callback Queue) 라는 곳에 보관하다가
호출 스택이 완전히 비워졌을 때 이벤트 루프가 호출 스택으로 보낸다. 호출 스택에 쌓인 스택 프레임은 다시 처리된다.
이러한 이벤트 루프 및 콜백 큐로 인해 단일 스레드인 자바스크립트가 멀티 스레드처럼 동시성이 이루어진다.
이벤트 루프와 콜백 큐는 자바스크립트에서 지원해주는게 아닌 브라우저 및 node.js에서 지원해준다.
아래 예시를 보자.
function test() {
console.log("1");
}
function test2() {
console.log("2");
}
test();
setTimeout(function(){
console.log("3");
}, 2000);
test2();
자바스크립트는 단일 스레드 방식이고 하나의 호출 스택(Call Stack) 으로 인해 한번에 하나의 일만 처리 한다고 했다.
위 코드를 보면 test() 가 실행되고 setTimeout이 2초 뒤에 console.log("3");을 실행, 마지막으로 test(); 함수가
실행되는걸로 예상 가능하다. 하지만 예상과 다르게 아래와 같은 결과가 나온다.
1, 3, 2 라는 결과 값이 나올거 같지만 아니였다.
이유는 아래와 같다.
1. 가장 먼저 test(); 함수가 호출 스택(Call Stack)에 쌓이게 된다.
아래의 이미지를 보면 test(); 함수를 실행하게 되면 test() 함수와 test() 함수의 console.log("1"); 가 Call Stack 에
쌓이게 됩니다.
2. Console 창에 test() 함수의 console.log("1") 를 실행하고 test() 함수 종료한다.
3. 종료된 test() 함수 다음으로 setTimeout()이 Call Stack 으로 들어오게 된다.
4. 비동기 함수인 setTimeout() 처리 및 test2() 함수 실행
setTimeout() 은 비동기 함수이므로 바로 Call Stack 에서 처리하지 않고 Web API에서 처리하도록 보낸다.
그리고 test2() 함수와 test2() 함수의 console.log("2") 가 호출되어 Call Stack 에 쌓인다.
여기서 test2() 함수 및 console.log("2") 가 처리되고 나서 Web API의 setTimeout() 을 처리하는게 아니다.
Web API로 넘어간 순간부터 setTimeout() 이 실행된다.
즉, test2() 함수를 실행하면서도 setTimeout()은 실행되고 설정된 2초가 지나가고 있는것이다.
5. test2() 함수 실행
6. setTimeout() 실행
setTimeout() 은 Web API로 넘어간 순간부터 실행되고 실행 완료된 함수는 대기를 위해 콜백 큐(Callback Queue)로
들어가게 된다.
7. 이벤트 루프로 인해 콜백 큐(Callback Queue)에 들어가 있는 setTimeout() 호출 스택(Call Stack) 으로 넘김
이벤트 루프는 호출 스택(Call Stack)이 완전 비워진걸 확인하면 콜백 큐(Callback Queue) 에서 순차적으로
호출 스택(Call Stack) 으로 넘겨 처리한다.
* 호출 스택(Call Stack)이 완전히 비워진 후에 콜백 큐는 호출 스택으로 넘긴다.
8. setTimeout() 및 console.log("3") 처리