이번 게시물에서는 프로젝트를 진행하거나 타인의 코드를 보다보면 쉽게 볼 수 있는 비동기 처리를 위한 async/await에 대해서 학습할 것이다.
학습하기 전 저의 async/await 인식 정도는...
async를 붙이면 비동기처럼 작동하게 한다. await를 붙이면 해당 코드가 완료될 때까지 다음 코드가 진행되지 않는다. async가 없으면 await를 붙일 수 없다. await를 사용하여 계산이 완료되지 않은 데이터를 의도와 다르게 전송되지 않도록 한다 정도의 지식만 가지고 코드를 읽거나 작성했었다.
node.js로 처음 개발을 시작할 때부터 사용해 왔던, 찝찝한 마음으로 알아봐야겠다는 생각만 하고 다른 것에 치여 미뤄둔 문법이었다. 이번 학습이 명확한 생각을 갖고 동기/비동기를 다루는 계기가 되었으면 한다.
동기/비동기 시리즈의 마지막인 async/await ☠️
asynchronous(비동기)를 어떻게 await(기다리다)하게 만드는 건지 학습해 보자
callback 함수에서는 함수 안의 함수, 그 안에 함수, 또 그 안에 함수 등을 활용하면서 순차적인(동기적인) 코드의 진행을 만들었다.
Promise를 다뤘을 때에는, Promise 객체의 연산 성공이나 실패를 .then()과 .catch()를 사용함으로써 순차적인(동기적인) 진행을 만들었다.
async와 await는 우리가 Promise 객체를 좀 더 편하게 사용할 수 있게 도와줄 것이다.
callback 함수는 콜백지옥이라는 코드의 복잡함을 야기할 수 있었고
Promise에서 .then()과 .catch()는 콜백 함수를 인자로 사용하기 때문에 가독성에 약간의 아쉬움을 줬다.
async/await는 보다 읽기에 자연스러운, 즉 동기적 흐름처럼 이어지는 것을 느낄 수 있을 것이다.
(callback 함수나 Promise의 .then() .catch()가 나쁘다는 것이 절대 아니다. 도구의 활용은 항상 필요에 의해 상황에 맞게 쓰는 것이 옳은 선택일 것이다.)
그럼 async/await에 대한 몇 가지 사실들을 알아봅시다.
1) 비동기 함수를 만들기 위해 function() 앞에 async를 추가해 준다.
2) async function()은 await가 비동기 코드를 호출할 수 있게 해주는 함수다.
3) async 함수를 실행하게 되면 무조건 Promise 객체가 반환된다.
4) async 함수 내에서 return은 반환된 Promise 객체의 결과 값이다.
5) await 키워드는 반드시 async 함수 안에서만 사용 가능하다.
6) javascript가 await를 만나면 해당 함수의 Promise 상태가 이행될 때까지 기다렸다가 그 결과 값을 반환하고 다음을 실행한다.
7) 오류 처리를 위해 try catch를 사용할 수 있다. try문에서 어떤 곳에서든지 에러가 발생하면 제어 흐름이 catch블록으로 넘어간다.
- 비동기적인 함수 앞에 '이 함수가 실행되길 기다려라'라는 뜻에서 await를 붙인다.
- await가 붙어있는 promise를 리턴하는 함수는 반드시 다른 함수 안에서 실행되어야 한다. 그리고 그 함수는 async라는 키워드가 붙어 있어야 한다. 없다면 syntax error가 발생한다.
👿 설명만 읽어보면 감이 잡히질 않는다. async/await를 활용한 코드를 살펴보자.
Promise 객체를 반환하는 timer() 함수가 존재한다고 했을 때
Promise를 다루기 위해 .then()을 사용한 코드와 async/await를 사용한 코드를 비교해 놓았다.
두 코드의 결과는 똑같다.
Promise는 콜백 함수로 인해 조금 지저분해 보일 수 있는 반면, async/await는 깔끔한 형태를 띠고 있는 것을 볼 수 있다.
직관적인 가독성도 async/await가 조금 더 좋은 것을 확인할 수 있다.

예제 2-1에 timer() 함수가 정의되어 있다.
예제 2-2의 코드를 보고 어떤 순서로 나타날 것인지 생각해 보자.


순서의 정답은
open
start
time:1000
time:2000
time:3000
end
close
이다.
∵ 설명
1) async로 정의된 run2() 함수 실행
2) run2()함수 안의 console.log('open') 실행
3) await run(); 실행 -> await 때문에 run2()의 진행은 멈추고 run()의 Promise 상태가 이행될 때까지 기다려야 한다.
4) run() 함수 안의 console.log('start') 실행
5) run()함수 안의 await timer(1000); 실행 후 변수 time에 그 값을 할당 -> await 때문에 timer의 1초를 기다려야 한다.
6) console.log('time:'+time); 실행
7) 5번과 6번 과정을 2번 반복, 대신 timer(2000) timer(3000)이 될 것이다. -> 2초 후 3초 후
8) console.log('end'); 실행
9) run() 함수가 끝났으므로 console.log('close') 실행
hint: 함수 promise()가 실행되면 console.log(3)이 호출되는 것을 유의한 상태로 마지막 예제를 이해해 보자.
console.log(1);
const promise = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(3);
resolve("two");
}, 3000);
});
};
const promiseTwo = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("one");
}, 1000);
});
};
console.log(2);
async function foo() {
const result = await promise(); // 프라미스가 이행될 때까지 아래 코드로 넘어가지 않음...
const resultTwo = await promiseTwo(); // 위 코드의 프로미스가 반환될 때까지 대기 후 시작
console.log(resultTwo); // 완료 되면 아래의 코드가 이어서 실행됨
const parellOne = promise(); // 아래의 타이머와 거의 동시에 시작됨.
const parelltwo = promiseTwo(); // 해당 프로미스 이행 값이 먼저 반환됨.(약 1초)
console.log(await parellOne);
console.log(await parelltwo); // 위에서 변수 parelltwo에 프로미스 객체가 먼저 할당되었지만, 위의 console.log가 선행되고나서 실행됨.
}
foo(); // 콘솔에 찍히는 순서는 -> 1 2 3 "one" 3 "two" "one"
동기/비동기, callback 함수, Promise, async/await 등을 이해하기 위해서 알아야 할 것이 참 많았다.
더욱 확실한 이해를 하기 위해서 호이스팅, 선언과 초기화, Callstack, Memory Heap, Queue, Event Loop 등을 학습해 보길 권장한다. 처음 봤을 때는 이해가 안 가던 것도 여러 가지 글을 읽고 영상을 보고 책을 읽으면서 지식을 넓혀가다 보면 봤던 글이라도 다르게 보이는 순간이 찾아오니 너무 욕심부리지 않고 꾸준히 학습하는 태도를 길러야겠다.
참고 자료:
| Promise 기초 (Javascript) (2) | 2023.08.29 |
|---|---|
| Callback 함수 (수정 중) (0) | 2023.08.17 |
| 자바스크립트 동기, 비동기 (기초) (0) | 2023.08.05 |