자바스크립트 동기/비동기, Promise, Async/Await 정리

오랜만에 벨로그 글을 쓸 겸, 노마드코더의 유튜브 클론 강의와 ES6 이론을 공부하며, Async/Await 부분이 도대체 무슨 의미인지 헷갈리기도 하고 핵심 내용이라고 판단되기도 하여 아예 벨로그에 기록하려고 한다.

1. 동기/비동기의 차이

동기, 비동기에 관한 설명은 개인적으로 ⬇️ 이 분 설명이 가장 정확하다고 생각이 된다.
동기(Synchronous)는 정확히 무엇을 의미하는걸까?
위 글에 따르면, 동기는 현재 작업의 응답과 다음 작업의 요청의 타이밍이 일치하는 것, 비동기는 반대로 현재 작업의 응답과 다음 작업의 요청의 타이밍이 일치하지 않는 것으로 정의할 수 있다. 한마디로, 코드가 작성된 대로 실행이 되어야 동기적이라고 할 수 있다. 바로 이전의 코드 (현재 작업)이 실행된 뒤 바로 토스하여 현재 코드 (다음 작업)이 실행된다고 할 수 있으니...

동기: 현재 작업의 응답과 다음 작업의 요청의 타이밍이 일치하는 것
비동기: 현재 작업의 응답과 다음 작업의 요청의 타이밍이 일치하지 않는 것

간단히 동기적인 방식과 비동기적인 방식의 예제를 구현해보면 다음과 같다.

동기적인 방식 (자바스크립트의 기본 방식이다.)

console.log("I want first");
console.log("I want second");
console.log("I want third");

실행 결과

보는 것처럼, "I want first"를 실행하고 난 뒤 "I want second", "I want third"와 같이 코드가 입력된 순서대로 코드가 실행되는 것을 확인할 수 있다. 필자는 이러한 과정을 동기의 뜻인 현재 작업의 응답과 다음 작업의 요청의 타이밍이 일치하는 것과 일맥상통한다고 이해하였다. 그렇다면, 비동기를 보자.

비동기적인 방식

console.log("I want first");
setTimeout(() => console.log("I want second"), 1000);
console.log("I want third");

실행 결과

이번에는 "I want third"가 먼저 나오고, 그 다음에야 "I want second"가 나온다. 동기적인 방식이라면, "I want second"가 출력되기까지 기다린 다음에 "I want third"가 출력되어야 할 것이다. 그러나 비동기 방식은, 시간이 걸리는 작업은 따로 빼두고 빨리 끝낼 수 있는 "I want third"부터 출력하고 그 다음 작업이 끝난 "I want second"를 출력한다.

어떻게 이런 일이 가능한 것인가?

위 설명에 대해서는 얄팍한 코딩사전 님이 자세히 설명해주셨다.
자바스크립트는 기본적으로 스레드가 하나인 싱글 스레드라는 사실을 잘 알 것이다. 그러나 실제로 웹브라우저에서 자바스크립트를 실행하면, 자바스크립트의 스레드 이외에도 Web API라는 통로가 존재한다. 이곳에서는 시간이 걸리는 작업이나 Ajax 통신, 파일에서 데이터를 읽어오는 작업 등을 처리한다. (시간 소요가 적은 것 대로 처리를 하며, 이 처리가 이루어지는 게 이벤트 루프이다.)
setTimeout과 같은 작업들이 이곳으로 이동되어 처리되는 동안, 자바스크립트는 자신 본연의 일을 (console.log와 같은) 바로 바로 진행하는 것이다.

2. Promise

Callback

Promise는 흔히 말하는 Callback 지옥을 해결하기 위해 나온 기능인데, 먼저 Callback이란 무엇인가를 생각해보자.
Callback (콜백)은 간단히 말하면, 함수의 인자 안에 있는 함수를 의미한다.
위에서 작성했던 setTimeout 코드를 다시 보자면

setTimeout(() => console.log("I want second"), 1000);

여기에서 arrow function으로 정의된 () => console.log("I want second") 이 부분이라고 할 수 있다. 저 한 줄의 의미는, 1000ms의 시간이 지나면, "I want second"를 출력하는 함수를 실행해. 라고 명령하는 것이다.
또 다른 예시에서도 콜백 함수의 예시를 쉽게 찾을 수 있다. 바로 이벤트 함수이다.

button.addEventListener("click", handleClick);

위 표현도, 버튼을 클릭하면, handleClick이라는 함수를 실행해. 라고 명령하는 것이다.
어떤 게 행해지고 나면 실행이 되는 함수라고 정의할 수 있겠다.

콜백 지옥 (Callback Hell) 😱

콜백 지옥은 이러한 콜백이 연속적으로 매우 많이 일어나는 현상을 뜻한다.

(간단한 예제라 setTimeout을 설정하지 않긴 했지만, 만약 DB에서 admin이라는 데이터를 불러온 것이라던지, admin을 불러오는 데 시간이 걸린다던지 하면 비동기 처리가 될 것이다.)
간단한 예제이긴 하지만, 콜백 함수가 많아지면 어디에서 에러가 났는지 파악하기 어렵고, 유지보수가 힘들다. 이러한 문제점을 해결하기 위해 등장한 개념이 Promise이다.

Promise

promise는 resolve, reject 함수를 인자로 가진다. resolve와 reject는 각각 성공, 실패 시 작동되는 함수로써, promise 객체를 반환한다.

const sample = new Promise((resolve, reject) => {
	setTimeout(resolve("completed"), 1000);
});
sample.then((msg) => console.log(msg));

위 코드가 의미하는 바는, (setTimeout과 같은) 비동기 작업을 마치면(then) 콜백을 수행하라 (msg) => console.log(msg)는 약속 (promise)이다.
then을 이용하면 순차적으로 실행하게끔 작성할 수 있어, 기존 콜백으로 작성할 때 보다 직관적이라고 할 수 있다.

3. Async / Await

async / await는 Promise를 대체하는 ES7의 새로운 기능이다.

일반 Promise

Promise를 활용한 코드는 이런 형태이다.

function hello(){
	return New Promise((resolve, reject) => {
    	resolve("hello");
    });
};

Promise.then의 문제

그런데, 일반 promise는 then을 연속적으로 작성하게 되면 콜백 지옥 때와 같은 사태가 발생할 수 있다.

function hello(){
	return getName().then(name => {
    	return getAge().then(age => console.log(name, age));
    };
};

Async / Await

async를 활용한 코드는 다음과 같다. 자동으로 promise를 반환하므로 가독성이 더 좋아진다.

async function hello(){
	return "hello";
};

await는 async function 안에서만 작성할 수 있으며, 해당 코드가 완료되기까지 기다리라는 의미이다.
앞의 then의 남발을 해결하기 위해 async await 구문으로 변경해보자면,

async function hello(){
	const name = await getName();
    const age = await getAge();
    console.log(name, age);
};

⬆️ 이렇게 변경할 수 있다.

결론

console.log("aaaa");
비동기_작업_함수();
console.log("bbbb");

큰 틀에서 봤을 때, aaaa -> bbbb -> 비동기 작업 함수 실행이라는 흐름은 변하지 않는다.
Promise와 Async / Await은, 비동기 작업 함수 안에서의 흐름을 동기적으로 작성하도록 하는 기능인 것이다.

  1. 자바스크립트는 시간이 걸리는 작업, 파일에서 데이터를 가져오는 작업 등을 제외한다면 동기적으로 작동된다. 만약 언급한 작업 같은 것이 실행된다면, 그런 경우에 한해서만 Web API를 활용하여 비동기적으로 작동된다.
  2. Promise는 비동기 함수에서 주로 이용되는 콜백 방식의 단점인 콜백 지옥을 해결하기 위해 나온 기능이다.
  3. 그러나 Promise 또한 then을 남발하면 콜백 지옥과 비슷한 현상이 발생하기 때문에, 이를 보완하기 위해 나온 것이 Async / Await이다.

어느 정도 핵심적인 내용은 정리가 된 것 같은데, 아직도 뭔가 잘 풀리지 않은 것 같은 찝찝한 느낌이 든다. 추후에 더 공부하여 작성 및 보완할 필요가 있어 보인다.

Reference

좋은 웹페이지 즐겨찾기