비동기 프로그래밍 (콜백, Promise, async/await)
자바스크립트에서 비동기 프로그래밍은 웹 애플리케이션의 성능과 사용자 경험을 향상시키는 핵심 개념입니다.
이 절에서는 비동기 프로그래밍의 기본 개념부터 고급 패턴까지 상세히 알아보겠습니다.
비동기 프로그래밍의 필요성
비동기 프로그래밍은 시간이 걸리는 작업(예 : 네트워크 요청, 파일 입출력)을 처리할 때 중요합니다.
동기 코드는 이러한 작업을 기다리는 동안 전체 프로그램을 블록하지만, 비동기 코드는 다른 작업을 계속 처리할 수 있게 합니다.
예시
// 동기 코드
console.log("시작");
const result = longRunningOperation(); // 이 작업이 완료될 때까지 대기
console.log(result);
console.log("끝");
// 비동기 코드
console.log("시작");
longRunningOperation((result) => {
console.log(result);
});
console.log("끝"); // longRunningOperation이 완료되기 전에 실행됨
콜백 함수
콜백은 비동기 작업이 완료된 후 실행될 함수입니다.
function fetchData(callback) {
setTimeout(() => {
callback("데이터");
}, 1000);
}
fetchData((data) => {
console.log(data);
});
콜백 지옥
복잡한 비동기 로직은 중첩된 콜백을 야기하여 "콜백 지옥"을 만들 수 있습니다.
fetchData1((data1) => {
fetchData2(data1, (data2) => {
fetchData3(data2, (data3) => {
// 더 많은 중첩...
});
});
});
이는 코드의 가독성과 유지보수성을 저하시킵니다.
Promise
Promise는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체입니다.
Promise 상태
- Pending : 초기 상태, 이행되거나 거부되지 않은 상태
- Fulfilled : 작업이 성공적으로 완료된 상태
- Rejected : 작업이 실패한 상태
기본 사용법
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("성공!");
// 또는
// reject("실패!");
}, 1000);
});
myPromise
.then((result) => {
console.log(result); // "성공!"
})
.catch((error) => {
console.error(error);
})
.finally(() => {
console.log("Promise 완료");
});
Promise 체이닝
Promise는 연속된 비동기 작업을 처리하기 위해 체이닝할 수 있습니다.
fetchData1()
.then(data1 => fetchData2(data1))
.then(data2 => fetchData3(data2))
.then(data3 => {
console.log(data3);
})
.catch(error => {
console.error(error);
});
Promise 정적 메서드
- Promise.all() : 모든 Promise가 이행될 때까지 기다립니다.
Promise.all([fetchData1(), fetchData2(), fetchData3()])
.then(([result1, result2, result3]) => {
console.log(result1, result2, result3);
});
- Promise.race() : 가장 먼저 이행되거나 거부되는 Promise의 결과를 반환합니다.
Promise.race([fetchData1(), fetchData2()])
.then(result => {
console.log("가장 빠른 결과:", result);
});
async/await
async/await는 Promise를 기반으로 한 더 직관적인 문법입니다.
async function fetchAllData() {
try {
const data1 = await fetchData1();
const data2 = await fetchData2(data1);
const data3 = await fetchData3(data2);
console.log(data3);
} catch (error) {
console.error(error);
}
}
fetchAllData();
Promise와의 관계
- async 함수는 항상 Promise를 반환합니다.
- await는 Promise가 이행될 때까지 함수의 실행을 일시 중지합니다.
장점
- 더 동기적으로 보이는 코드
- 에러 처리가 더 직관적 (try-catch 사용)
단점
- 오래된 브라우저에서는 지원되지 않을 수 있음
- 병렬 실행에는 추가적인 처리 필요
에러 처리
async/await에서의 에러 처리
async function fetchData() {
try {
const data = await fetch('https://api.example.com/data');
const json = await data.json();
console.log(json);
} catch (error) {
console.error('데이터 가져오기 실패:', error);
}
}
실제 웹 개발 시나리오
- API 호출
async function getUserData(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const userData = await response.json();
return userData;
} catch (error) {
console.error('Error fetching user data:', error);
throw error;
}
}
// 사용
getUserData(123)
.then(user => {
console.log('User:', user);
})
.catch(error => {
console.error('Failed to get user:', error);
});
- 파일 처리 (Node.js 환경)
const fs = require('fs').promises;
async function processFile(filePath) {
try {
const data = await fs.readFile(filePath, 'utf8');
const processedData = data.toUpperCase();
await fs.writeFile(filePath + '.processed', processedData);
console.log('File processing complete');
} catch (error) {
console.error('Error processing file:', error);
}
}
processFile('example.txt');
비동기 프로그래밍은 자바스크립트의 핵심 특징 중 하나로, 특히 웹 개발에서 중요한 역할을 합니다. 비동기 코드를 통해 시간이 오래 걸리는 작업을 처리하면서도 사용자 인터페이스의 응답성을 유지할 수 있습니다.
콜백 함수는 비동기 프로그래밍의 가장 기본적인 형태입니다. 그러나 복잡한 비동기 로직을 처리할 때 콜백 지옥이라는 문제에 직면할 수 있습니다. 이는 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만듭니다.
Promise는 이러한 문제를 해결하기 위해 도입되었습니다. Promise는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체로, 체이닝을 통해 연속된 비동기 작업을 더 깔끔하게 표현할 수 있게 해줍니다. 또한 Promise.all()과 Promise.race() 같은 정적 메서드를 통해 여러 비동기 작업을 효과적으로 관리할 수 있습니다.
async/await는 Promise를 기반으로 한 더 현대적이고 직관적인 문법입니다. 이를 사용하면 비동기 코드를 마치 동기 코드처럼 작성할 수 있어, 가독성이 크게 향상됩니다. 또한 try-catch 구문을 사용하여 에러 처리를 더 자연스럽게 할 수 있습니다.
실제 웹 개발에서 이러한 비동기 패턴들은 API 호출, 파일 처리, 데이터베이스 쿼리 등 다양한 시나리오에서 활용됩니다. 예를 들어, 사용자 데이터를 가져오거나 파일을 처리할 때 async/await를 사용하면 코드를 더 명확하고 관리하기 쉽게 작성할 수 있습니다.
비동기 프로그래밍을 마스터하는 것은 효율적이고 반응성 높은 웹 애플리케이션을 개발하는 데 필수적입니다. 콜백, Promise, async/await 각각의 특징과 적절한 사용 상황을 이해하고, 이를 실제 개발에 적용할 수 있는 능력이 중요합니다. 또한 비동기 코드의 에러 처리에 특별히 주의를 기울여야 하며, 적절한 에러 핸들링 전략을 구현하는 것이 안정적인 애플리케이션 개발의 핵심입니다.