자바스크립트에서 비동기가 왜 중요할까?
자바스크립트는 single-threaded
이기 때문이다.
single threaded / single callstack란?
- 쓰레드가 하나라는 말이다.
- 쓰레드 하나에 하나의 callstack이 존재한다.
- 하나의 callstack은 한 번에 하나의 작업만을 실행한다. 실행해야 하는 작업해야 할 일들이 많을 경우 javascript는 순서대로 일을 실행해야 하기 때문에 시간이 걸리는 작업이 있을 경우 이전 작업이 완료될 때까지 다음 작업을 실행할 수 없다. 그렇기 때문에 javascript에서 비동기를 중요하게 다룬다. 예를 들어, 우리가 음식점에 가서 음식을 주문한다 가정해보자. 손님 1이 음식 주문을 하고 음식이 조리될 때까지의 시간이 30분 정도 걸린다고 했을 때 손님 2는 손님 1이 주문한 음식을 받기 전까지 음식을 주문할 수 없는 것을 동기적이라고 할 수 있다. 손님 1이 주문을 하고 음식을 조리하는 동안 손님 2도 주문을 할 수 있게끔 비동기적으로 처리해준다면 상황이 더 유연해질 것이다.
event loop / web APIs / callbacke que
V8 Engine
자바스크립트 엔진의 대표적인 예는 구글에서 만든 Google V8 엔진이다. 브라우저에서 자바스크립트를 실행하는 엔진이다.
- heap (저장되는 장소)
- stack (callstack) (실행문들이 쌓이는 구조)
Web API
Ajax, setTimeOut, event 등을 의미한다. 브라우저에서 지원한다.
Web API는 요청한 작업을 받아 순서대로 Callback Queue에 줄을 세워 넣는다.
Call Stack
콜 스택(call stack) 이란 컴퓨터 프로그램에서 현재 실행 중인 것들에 관한 정보를 저장하는 스택 자료구조이다. 그냥 줄여서 스택 (the stack) 이라고도 한다.
- 함수가 실행되면 스택 구조에 쌓인다.
- 함수가 종료되면 스택에서 빠져나간다.
아래와 같이 코드가 실행될 때에 스택 구조에 쌓이게 된다. 함수가 종료되지 않으면 스택에서 빠져나가지 않고 그 위에 실행되는 다른 함수가 그 위에 쌓이게 되는 구조이다. 함수가 종료되어야만 스택에서 빠져나갈 수 있다.
- main() 프로그램이 실행된다.
- foo(5)를 실행하면 콜스택에 쌓이게 된다.
- foo(5)는 sum(cd) 함수가 실행된 값을 변수에 담는다. sum(cd) 함수가 실행하면 foo(5) 함수가 종료되지 않았기 때문에 foo(5) 함수 그 위에 sum(c*d) 함수가 콜스택에 쌓이게 된다.
- sum(c*d) 함수가 결과를 받고 종료되면 스택에서 빠져나온다.
- foo(5) 함수도 결과를 받으면서 스택에서 빠져나온다.
- main() 프로그램이 종료된다.
참조 - loop
위 링크를 타고 들어가 직접 코드를 실행해 보면 Call Stack과 Callback Queue, event loop 가 내부적으로 어떻게 실행되는지 표현해 준다.
콜스택 오버 에러!!
재귀함수의 예
1
2
3
function foo () {
foo();
}
재귀 함수를 사용할 때 탈출 조건을 꼭 만들어 주어야 한다. 그렇지 않으면 무한 루프를 돌다가 결국 콜스텍 오버 에러를 낸다.
foo()
함수가 실행되고 종료되지 않은 채 foo()
함수를 실행하기 때문에 콜스택에 foo()
가 계속 무한히 쌓이게 되면 RangeError: Maximum call stack size exceeded
에러 메시지가 뜨게 된다.
Callback Queue와 event loop
- callback Queue : web API는 요청한 작업들을 callback Queue에 줄을 세워 넣어주고 스택 구조에 넣어지기 전 대기하고 있는 곳이다.
- Event Loop : stack과 callback Queue를 지속적으로 보고 있다가 스택이 비어있으면 callback Queue로부터 스택에 넣는다.
함수가 실행될 때에 스택 구조에 쌓이게 되고 비동기 함수가 있을 경우 web API에 요청을 보내게 된다. web API는 요청한 작업들을 callback Queue에 줄을 세워 넣어주고, 스택 구조에 쌓인 함수들이 모두 실행되고 나서 스택을 빠져나가게 되면 event loop는 지속적으로 스택과 callback Queue를 지속적으로 지켜보다가 스택이 비워지면 callback Queue에 대기하고 있던 작업들은 스택 구조에 넣어 실행시켜준다.
examples
1
2
3
4
5
6
7
console.log(1);
setTimeout(function foo () {
console.log(2);
}, 2000);
console.log(3);
위와 같은 코드가 있을 경우 콘솔에 1, 3, 2 순서대로 아래와 같이 실행된다. foo 함수는 비동기 함수로 callback Queue에 넣어지고 스택에 있는 함수들을 모두 실행한 다음 스택에 빠져나왔을 때 프로그램은 종료되지만 Event Loop가 스택이 비어있으면 callback Queue로부터 줄이 세워진 함수들을 차례로 스택에 넣어주기 때문에 마지막으로 콘솔에 2가 찍히게 된다.
- main() 프로그램이 실행된다.
- console.log(1)을 실행하면 콜스택에 쌓이고 콘솔에 1이 찍히고 콜스택을 빠져나온다.
- setTimeout(foo, 2000) 비동기 콜백 함수를 callback Queue에 넣는다.
- console.log(3)를 실행하면 콜 사택에 쌓이고 콘솔에 3가 찍히고 콜스택을 빠져나온다.
- main() 프로그램이 종료된다.
- 2초 후 event loop가 callback Queue를 보고 있다가 스택이 비어있으면 callback Queue로부터 스택에 foo() 함수를 넣는다.
- 콘솔에 2가 찍히고 foo() 함수가 종료되면서 콜스택을 빠져나온다
example
유명한 클로저 예제이다.
1
2
3
4
5
for (var i=1; i < 5; i++) {
setTimeout(function (i) {
console.log(i)
}, 3000);
}
위 코드를 실행하면 3초 후 콘솔에 5가 5번 찍히게 된다. setTimeout 비동기 함수는 콜백 큐에 들어가 있을 때 i가 모두 증가된 후 setTimeout 비동기 함수가 실행되기 때문에 콘솔에 5만 찍히게 된다. i가 순차적으로 증가되게끔 콘솔에 찍고 싶다면 어떻게 해야 할까?
1
2
3
4
5
6
7
for (var i = 1; i < 6; i++) {
(function(ct) {
setTimeout(function () {
console.log(ct);
}, 3000);
})(i);
}
위와 같이 함수로 스코프를 만들어주면서 i를 인자로 받아 순차적으로 실행해주면 i를 순차적으로 콘솔에 찍을 수 있다. (let
을 사용하지 않았을 경우)
Highger Order Funcion / 고차원 함수
- 함수를 인자로 받거나 함수를 값으로 갖는 함수를 고차원 함수라고 한다.
- 자바스크립트에서 함수는 first-class objects 일등급 객체이다. 일등급 객체란? 자료구조에 저장할 수 있고, 함수의 매개변수로 넣어 줄 수 있고, 함수에서 리턴해줄 수 있고, 실행문 중간에 언제든지 생성할 수 있는 것. 고차원함수가 존재할 수 있는 이유는 자바스크립트에서 함수는 일등급 객체이기 때문에 비동기를 가능하게 해준다.