오늘은 저번에 정리했었던 Promise와 async/await 문법에 대해서 복습을 하며 조금 더 깊게 파보는 시간을 가져보겠습니다.
저번에 동기/비동기를 다루며 공부를 했었습니다만 그때에는 그저 비동기 작업이 중첩되는 콜백 함수를 해결하기 위해 이해없이 그냥 암기로 두 문법을 사용했다면 이번에는 비동기 작업들을 동기 작업 처럼 표현하며 그에 따른 내부적 로직에 대해 자세히 공부하였습니다.
그럼 예시 코드를 보며 공부하겠습니다.
함수의 인자를 계속 콜백하며 정보를 가져오며 결국엔 콜백 지옥에 빠지는 모습입니다.
function getUserData(callback) {
setTimeout(() => {
console.log('사용자 데이터를 가져왔습니다');
callback();
}, 1000);
}
function processUserData(callback) {
setTimeout(() => {
console.log('사용자 데이터를 처리했습니다');
callback();
}, 1000);
}
function displayUserData() {
setTimeout(() => {
console.log('사용자 데이터를 화면에 표시했습니다');
}, 1000);
}
getUserData(() => {
processUserData(() => {
displayUserData();
});
});
이 코드는 비동기 코드로 순서를 억지로 만들어준 모습입니다.
이 경우에는 콜백 지옥을 통하여 순서를 강제적으로 결정할 수 있습니다.
하지만 독립적으로 실행되는 여러 비동기 함수가 있다면 이들을 호출 할 때에 순서에 문제가 생길 수 있습니다. 이를 해결하기 위해 promise와 async/await문법을 활용하여 비동기 작업을 동기 작업 처럼 표현 할 수 있습니다.
이 문법들은 실제 비동기 동작을 바꾸지 않고 복잡한 콜백 지옥을 간결하고 읽기 쉽게 만들어 줍니다.
Promise문법 예시를 통하여 코드 실행 순서를 따라가 보겠습니다.
function getUserData() { // 유저 데이터를 불러오는 함수입니다.
return new Promise((resolve) => { // 생성자의 인자를 받아옵니다.
setTimeout(() => {
console.log('사용자 데이터를 가져왔습니다');
resolve(); // promise 상태를 대기(pending)에서 성공(fulfilled)로 상태 전환을 해줍니다.
}, 1000);
});
}
function processUserData() { // 유저 데이터를 처리하는 함수입니다.
return new Promise((resolve) => { // 생성자의 인자를 받아옵니다.
setTimeout(() => {
console.log('사용자 데이터를 처리했습니다');
resolve(); // promise 상태를 대기(pending)에서 성공(fulfilled)로 상태 전환을 해줍니다.
}, 1000);
});
}
function displayUserData() { // 유저 데이터를 화면에 표시하는 함수입니다.
return new Promise((resolve) => { // 생성자의 인자를 받아옵니다.
setTimeout(() => {
console.log('사용자 데이터를 화면에 표시했습니다');
resolve(); // promise 상태를 대기(pending)에서 성공(fulfilled)로 상태 전환을 해줍니다.
}, 1000);
});
}
getUserData() // getUserData 함수를 실행하여 promise의 상태를 반환합니다.
.then(() => processUserData()) // Promise : {getUserData}의 상태가 성공(fulfilled)이면 .then을 통하여 processUserData 함수를 실행합니다.
.then(() => displayUserData()); // Promise : {processUserData}의 상태가 성공(fulfilled)이면 .then을 통하여
displayUserData 함수를 실행합니다.
Promise의 상태는
가 있습니다. 이 상태는 호출 한 번에 상태 전환 한 번이 종속됩니다.
그렇다면 이 상태 그대로 async/await문법 예시를 통하여 실행 순서를 따라가 보겠습니다.
function getUserData() { // 유저 데이터를 불러오는 함수입니다.
return new Promise((resolve) => {
setTimeout(() => {
console.log('사용자 데이터를 가져왔습니다');
resolve({ name: '홍길동', age: 25 }); // 값들을 인자로 콜합니다.
}, 1000);
});
}
function processUserData(userData) { // 유저 데이터를 처리하는 함수입니다.
return new Promise((resolve) => {
setTimeout(() => {
console.log(`사용자 데이터를 처리했습니다: 이름은 ${userData.name}, 나이는 ${userData.age}입니다`);
resolve(`${userData.name}님의 데이터를 분석 완료`); // 값들을 인자로 콜합니다.
}, 1000);
});
}
function displayUserData(message) { // 유저 데이터를 화면에 표시하는 함수입니다.
return new Promise((resolve) => {
setTimeout(() => {
console.log(`결과를 화면에 표시했습니다: ${message}`);
resolve();
}, 1000);
});
}
const processUser = async () => { // 선언된 Promise 문법을 실행하는 함수입니다.
const userData = await getUserData();
// getUserData 함수를 실행하여 resolve()에 전달 된 값을
// 성공(fulfilled)하면서 호출되는 값이 콜백되어 userData 변수에 저장합니다.
const processedMessage = await processUserData(userData);
// userData 변수에 저장된 값을 인자로 받아 processUserData 함수를 실행하고,
// resolve()에 전달 된 값을 성공(fulfilled)하면서 호출되는 값이 콜백되어
// processedMessage 변수에 저장합니다.
await displayUserData(processedMessage);
// processUserData에 전달된 값을 인자로 받아 displayUserData 함수를 실행합니다.
}
processUser();
정확한 로직을 이해하고 사용하니
그저 외우며 콜백 지옥을 해결 할 때와는 다른 관점으로 코드를 볼 수 있게 되는 것 같습니다. 추가로 async/await 문법은 promise문법의 모습만 바꾼것으로 안에 사용하고 있는 로직은 비슷합니다.