코드를 갖다 쓰기만 해서는 도무지 이해가 안 가는 JavaScript의 비동기 처리를 알아보자! 관성처럼 .then()과 .catch()를 작성하던 과거의 나를 반성한다...
동기, 비동기에 대해서는 잘 정리된 글들이 있으니 참고해보면 좋을 것!
동기 / 비동기가 궁금하다면? 동기와 비동기의 차이
Promise
는 콜백지옥의 대항마로서(^^;) ES6에서 처음 등장했다.
기본 구조를 먼저 보자.
let myPromise = new Promise((resolve, reject) => {
...
});
myPromise.then(() => {
...
}).catch(() => {
...
})
new Promise()를 통해 사용할 수 있고, 뒤에 들어오는 화살표 함수에 들어가는 내용이 executor
라는, 실행되는 부분이라고 생각하면 된다. 뒤에 들어가는 executor
함수는 resolve와 reject라는 인자 중 하나를 호출해 Promise 객체의 상태를 변경한다.
대기(pending): 이행하거나 거부되지 않은 초기 상태
이행(fulfilled): 연산이 성공적으로 완료된 상태
거부(rejected): 연산이 실패한 상태
Promise 객체는 초기에 대기(pending) 상태이며, 비동기 작업이 완료되면 이행(fulfilled) 또는 거부(rejected) 상태로 바뀐다.
Promise가 이행 상태가 되었을 때
resolve
가 호출되며 그 결과값이then()
메서드로 전달되고, 거부 상태가 되면reject
가 호출되어catch()
메서드로 결과값을 전달한다.
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
console.log('start');
delay(1000)
.then(() => console.log('1 second later'))
.then(() => delay(2000))
.then(() => console.log('3 seconds later'))
.catch((error) => console.error(error))
.finally(() => console.log('done'));
실행하면 아래와 같은 결과를 얻을 수 있다.
코드를 보면 .then이 쭈우욱 나오는 걸 볼 수 있는데, 이와 같이 then() 과 catch() 문의 체이닝을 통해 로직을 명확히 해나갈 수 있다.
하지만 어딘가 익숙한 이 냄새. 콜백 지옥과 같은 이 냄새.
그래서 나왔다 async/await!
ES8에서 도입된 개념으로, 프로미스를 기반으로 한 비동기 처리 방식이다. 이를 이용하면 코드가 간결해지고 가독성이 좋아진다고 하는데! 한 번 봐보자.
프로미스를 기반으로 하고 있기 때문에 사용방법이 크게 다르지는 않다.
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId === 123) {
resolve({ id: userId, name: "감자" });
} else {
reject(new Error("유저 없다"));
}
}, 1000);
});
}
async function printUserName(userId) {
try {
const userData = await fetchUserData(userId);
console.log(`User name: ${userData.name}`);
} catch (error) {
console.error(error.message);
}
}
printUserName(123); // Output: User name: 감자
printUserName(456); // Output: User not found
async로 감싼 printUserName() 함수에서 try-catch를 사용하여 Promise의 결과를 처리한다. await 키워드는 Promise가 완료될 때까지 함수의 실행을 일시 중지하는 역할을 한다.
그런데 가독성이 좋다..? 잘 와닿지 않는다.
더 쉬운 예시를 보자.
console.log('1');
setTimeout(() => console.log('2'), 1000);
console.log('3');
1이 출력되고 1초 후에 2가 순서는 1, 3, 2로 출력된다. 하지만 위에서 아래의 순서로 읽히지 않는다.
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const asyncFunction = async () => {
console.log('1');
await delay(1000);
console.log('2');
console.log('3');
};
asyncFunction();
이렇게 작성하면 1이 출력되고 1초 후에 2가 출력, 이후 3 출력으로 보장되며 위에서 아래로 읽힌다. 이렇게 비동기 함수를 동기적으로 읽을 수 있는 부분에 있어서 가독성이 좋다고 하는듯하다.
정리하면서도 감이 올듯말듯해 명확히 설명하기가 아직 어려운 것 같다. 이번에 최대한 비동기 함수를 정확하게 써보면서 이해도를 높여보겠다!