비동기란?
내게 동기와 비동기 차이와 웹상에서 비동기처리를 하는 경우에 대하여 대답해 보라고 한다면
동기는 모든 작업을 순차적으로 실행하는 것,
비동기는 순차적으로 실행하지 않고, 독립적으로 작업하여 완료하는 대로 실행하는 것.
비동기 처리는 필요에 의해 외부 API서버와의 통신을 하는 경우? 라고 말할것 같다.
이번 노트에서는 자바스크립트에서 비동기 동작원리에 대하여 작성해 보았다.
JavaScript에서 비동기 동작
이전 노트 자바스크립트 v8동작원리에서 자바스크립트 런타임에 대하여 작성하면서 이벤트루프와 콜백 큐에 대하여 적었었다.

Event Loop와 Callback Queue
- Callback Queue는 실행할 콜백 함수들의 리스트, Event Loop는 Call Stack에 Callback Queue의 메시지를 전달해주는 역할을 한다.
- 버튼 클릭 같은 이벤트나 DOM 이벤트, http 요청, setTimeout 같은 비동기 함수는 Web API를 호출하며 Web API는 콜백 함수를 콜백 큐에 밀어 넣는다.
- 콜백 큐는 대기하다가 스택이 비는 시점에 이벤트 루프를 돌려 해당 콜백 함수를 스택에 넣는다.
- 이벤트 루프의 기본 역할은 큐와 스택 두 부분을 지켜보고 있다가 스택이 비는 시점에 큐의 콜백을 실행시켜 주는 것이다.
- 웹 브라우저에서는 이벤트가 발생할 때마다 메세지가 추가되고 이벤트 리스너가 첨부된다.
- 콜백 함수의 호출은 호출 스택의 초기 프레임으로 사용되며 자바스크립트가 싱글 스레드 이므로 스택에 대한 모든 호출이 반환될 때까지 처리가 멈춰져 있다.
비동기 처리의 필요성
위 이미지를 바탕으로 설명하자면 자바스크립트에서 비동기 처리가 동작하는 절차는 다음과 같다.
스크립트 문맥에서 자바스크립트 엔진이 동기로 코드를 순차적으로 실행하면서 비동기 로직을 만나면,
해당 로직은 독립적인 실행과 동시에 Call Stack에서 사라진다.
비동기 로직 작업이 완료되는대로 결과 로직이 Event Queue로 넘겨지며
동작하고 있던 Call Stack이 비는 시점에 Event Loop에 의하여 Queue에 쌓인 순서대로 Call Stack에 전달된다.
만약 콜스택 내에 수행시간이 긴 함수가 있으면 어떻게 될까?
콜스택에 수행할 함수가 있으면 브라우저는 사실 아무것도 할 수 없다.
브라우저는 렌더링을 할 수도 없고 다른 스크립트 코드를 수행할 수도 없고 그야말로 이도저도 못하는 상황이 되어버린다.
또, 외부 서버로 통신했는데 통신자체에 문제가 발생할 경우, 처리가 오래걸릴 경우 브라우저 동작도 그만큼 지연된다.
만약 수십개의 통신을 동기로 처리한다고 가정하면 완성된 브라우저를 띄우는데 많은 시간이 소요될 것이다.
비동기 처리의 문제점
비동기 처리가 특정 상황에서 필요하다는 것은 알겠지만 한가지 문제가 있다.
1console.log(1);23setTimeout(function() {4 console.log(2);5}, 1000);67console.log(3);
위의 코드를 실행하면 의도는 1 2 3이었지만, 1 3 2 순으로 출력될 것이다.
외부 API통신 예제를 보자.
1function loadDoc() {2 var test;3 var xhttp = new XMLHttpRequest();4 xhttp.onreadystatechange = function() {5 if (this.readyState == 4 && this.status == 200) {6 test = this.responseText;7 return tset // 위의 this.responseText를 기다려주지 않는다..8 }9 };1011 xhttp.open("GET", "https://jsonplaceholder.typicode.com/todos/1", true);12 xhttp.send();13}14console.log(loadDoc()); // undefined
이렇게 특정 로직의 실행이 끝날 때까지 기다려주지 않고 나머지 코드를 먼저 실행하는 것이 문제가 된다.
콜백함수의 활용
콜백함수를 활용한다면 비동기 처리의 결과를 순차적으로 동작시킬 수 있다.
위의 코드를 개선해보자.
1function loadDoc(callbackFunc) {2 var xhttp = new XMLHttpRequest();3 xhttp.onreadystatechange = function(response) {4 if (this.readyState == 4 && this.status == 200) {5 callbackFunc(this.responseText);6 }7 };89 xhttp.open("GET", "https://jsonplaceholder.typicode.com/todos/1", true);10 xhttp.send();11}1213loadDoc(function(response){14 console.log(response);15})
콜백 지옥(Callback Hell)
하지만 비동기 처리 결과에 대하여 여러 함수들을 복합적으로 사용해야할 경우가 발생하는데 이를 콜백지옥이라고 부른다.
1const getAuth = function() {2}3const connect = function() {4}5const show = function() {6} 7function loadDoc() {8 var xhttp = new XMLHttpRequest();9 xhttp.onreadystatechange = function(response) {10 if (this.readyState == 4 && this.status == 200) {11 getAuth(this.responseText, function(ip){12 connect(ip, function(response){13 show(response, function(response){14 console.log(response+"success")15 })16 })17 });18 }19 };2021 xhttp.open("GET", "https://jsonplaceholder.typicode.com/todos/1", true);22 xhttp.send();23}2425loadDoc()
확실히 복잡하고 가독성이 떨어진다.
위 절차는 조금 쉽게 풀어써보자.
1 | function getAuth(ip) { |
2 | connect(ip, show); |
3 | } |
4 | function connect(response) { |
5 | show(response) |
6 | } |
7 | function show(response) { |
8 | console.log(response+"success") |
9 | } |
10 | function loadDoc() { |
11 | var xhttp = new XMLHttpRequest(); |
12 | xhttp.onreadystatechange = function(response) { |
13 | if (this.readyState == 4 && this.status == 200) { |
14 | getAuth(this.responseText, connect); |
15 | } |
16 | }; |
17 | |
18 | xhttp.open("GET", "https://jsonplaceholder.typicode.com/todos/1", true); |
19 | xhttp.send(); |
20 | } |
21 | |
22 | loadDoc() |
위처럼 콜백지옥을 개선할 수 있지만 여전히 복잡하다.
이러한 복잡함을 해결해주기 위해 자바스크립트에서 Promise와 Async Await 객체를 제공한다.
Promise와 Async Await는 다음 노트에서!![]()

