[SeSACx코딩온] 비동기 처리가 필요한 이유
비동기 처리: 전체 통합
0. 왜 비동기 처리가 필요한가?
JavaScript는 기본적으로 동기적으로 작동합니다. 즉, 코드가 위에서 아래로 순차적으로 실행됩니다. 이러한 동기적 실행은 다음과 같은 문제를 일으킬 수 있습니다.
- 오래 걸리는 작업이 실행되는 동안 웹 페이지의 UI가 멈추게 됩니다. (UI 멈춤)
- 네트워크 요청이나 파일 읽기 같은 작업이 오래 걸리면 사용자는 웹 페이지가 응답하지 않는다고 느낄 수 있습니다. (사용자 경험 저하)
- 동기적 코드가 계속해서 차례로 실행되므로, 병렬로 실행할 수 있는 작업도 순차적으로 실행되어 성능이 저하됩니다. ( 성능 저하)
간단히 말해 동기적으로 작동하면 계산되는 사이 화면이 멈추는 등의 문제가 생길 수 있지만, 비동기 처리를 하면 복잡한 계산이 처리되는 사이 계산이 완료되길 기다리지않고 다음 작업을 할 수 있습니다. 이로 인해 UI가 멈추지 않고, 사용자 경험이 향상되며, 성능이 개선됩니다.
1. 필요한 이유, 코드로 보기
// 1. 동기적으로 데이터를 처리
function processData(data) {
console.log('데이터를 동기적으로 처리 중입니다...');
// 오랜 시간이 걸리는 계산 예시
for (let i = 0; i < 1000000000; i++) {
}
console.log('데이터를 동기적으로 처리 완료했습니다.');
return `처리된 데이터: ${data}`;
}
// 2. 비동기적으로 데이터를 처리 (for문을 Promise 내부로 이동)
function processData(data) {
console.log('데이터를 비동기적으로 처리 중입니다...');
return new Promise((resolve, reject) => {
setTimeout(() => { // 3초 후에 작업 시작
console.log('데이터를 비동기적으로 처리 완료했습니다.');
// 오래 걸리는 계산(for문)
console.log('오래 걸리는 계산을 시작합니다...');
for (let i = 0; i < 1000000000; i++) {
}
console.log('오래 걸리는 계산을 완료했습니다.');
resolve(`처리된 데이터: ${data}`);
}, 3000);
});
}
// processData 함수 호출
async function processData_async() {
console.log('비동기 처리를 시작합니다.');
try {
const result = await processData('예시 데이터');
console.log(result);
} catch (error) {
console.error('비동기 처리 중 오류 발생:', error);
}
}
processData_async();
--------------------------------------------------------
// 동기 처리 결과
데이터를 동기적으로 처리 중입니다...
// (오래 걸리는 계산으로 인해 멈춤)
데이터를 동기적으로 처리 완료했습니다.
처리된 데이터: 예시 데이터
--------------------------------------------------------
// 비동기 처리 결과
비동기 처리를 시작합니다.
데이터를 비동기적으로 처리 중입니다...
// (3초, 이때 다른 코드 처리하여 출력함)
데이터를 비동기적으로 처리 완료했습니다.
오래 걸리는 계산을 시작합니다...
오래 걸리는 계산을 완료했습니다.
처리된 데이터: 예시 데이터
--------------------------------------------------------
2. 비동기 처리 방법
1) 콜백 함수 (Callback Function)
(1) 개념
콜백 함수는 다른 함수의 인수로 전달되어 특정 작업이 완료된 후 실행되는 함수입니다. 비동기 작업이 끝난 후에 어떤 작업을 수행하도록 콜백 함수를 전달합니다. 콜백 함수는 비동기 작업이 완료되었을 때 호출되어 그 결과를 처리합니다.
(2) 실행 순서
- 주 함수가 실행됩니다.
- 콜백 함수를 인수로 받는 비동기 함수가 호출됩니다.
- 비동기 함수는 작업이 완료되면 콜백 함수를 호출합니다.
- 콜백 함수가 실행됩니다.
(3) 예제 코드
function goMart() {
console.log('마트에 가서 어떤 음료를 살지 고민한다..');
}
function pickDrink(callback) {
setTimeout(function() {
console.log('고민 끝');
const product = '제로콜라';
const price = 3000;
callback(product, price);
}, 3000);
}
function pay(product, price) {
console.log(`상품명: ${product} // 가격: ${price}`);
}
goMart();
pickDrink(pay);
(4) 분석
goMart
함수가 호출되어 “마트에 가서 어떤 음료를 살지 고민한다..”라는 메시지가 출력됩니다.pickDrink
함수가 호출됩니다. 이 함수는 3초 후에 내부의setTimeout
콜백 함수를 실행합니다.setTimeout
콜백 함수에서는 “고민 끝”이라는 메시지를 출력하고,product
변수에 ‘제로콜라’,price
변수에 3000을 할당합니다.pickDrink
함수의 콜백으로 전달된pay
함수가 호출됩니다. 이때product
와price
인자로 ‘제로콜라’와 3000이 전달됩니다.pay
함수가 실행되어 “상품명: 제로콜라 // 가격: 3000”이라는 메시지가 출력됩니다.
(5) 장단점
- 장점: 간단하고 직관적입니다. 작은 비동기 작업에는 적합합니다.
- 단점: 콜백 지옥(Callback Hell)으로 불리는 중첩 구조가 생기기 쉬워 가독성이 떨어지고, 코드가 복잡해질 수 있습니다.
2) Promise
(1) 개념
Promise는 비동기 작업의 성공 또는 실패를 처리하는 객체입니다. then
메서드로 작업이 성공했을 때의 결과를 처리하고, catch
메서드로 실패했을 때의 에러를 처리합니다. Promise는 작업이 성공(resolve)하거나 실패(reject)하는 경우를
설정하여 처리합니다.
(2) 실행 순서
- Promise 객체가 생성됩니다.
- 비동기 작업이 실행됩니다.
- 작업이 성공하면
resolve
가 호출되고, 실패하면reject
가 호출됩니다. then
또는catch
메서드로 결과를 처리합니다.
(3) 예제 코드
function goMart() {
console.log('마트에 가서 어떤 음료를 살지 고민한다..');
}
function pickDrink() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('고민 끝');
const product = '제로콜라';
const price = 3000;
if (product) {
resolve({ product, price });
}
// reject 예시, 현재 코드에서는 product가 항상 있으므로 굳이 필요없음.
// else {
// reject(new Error('제품을 찾을 수 없습니다.'));
// }
}, 3000);
});
}
function pay({ product, price }) {
console.log(`상품명: ${product} // 가격: ${price}`);
}
goMart();
pickDrink()
.then(pay)
.catch(function(err) {
console.error(err);
});
(4) 분석
goMart
함수가 호출되어 “마트에 가서 어떤 음료를 살지 고민한다..”라는 메시지가 출력됩니다.pickDrink
함수가 호출됩니다. 이 함수는 새로운 Promise를 반환합니다. 이 함수는setTimeout
을 통해 3초 후에 비동기 작업을 시작합니다.- 3초 후
setTimeout
의 콜백 함수가 실행됩니다. “고민 끝”이라는 메시지가 출력되고,product
변수에 ‘제로콜라’,price
변수에 3000을 할당합니다. pickDrink
함수의 내부에서resolve
가 호출되어 Promise가 성공 상태가 됩니다.then
메서드에 전달된pay
함수가 호출되며,product
와price
를 인자로 받아 “상품명: 제로콜라 // 가격: 3000”이라는 메시지가 출력됩니다.- Promise가 실패하지 않았으므로
catch
블록은 실행되지 않습니다.
(5) 장단점
- 장점: 가독성이 좋고 체인 형식으로 비동기 작업을 처리할 수 있습니다. 비동기 작업을 단계적으로 연결할 수 있어 복잡한 작업을 쉽게 처리할 수 있습니다.
- 단점: 여전히 콜백 지옥을 완전히 피하지 못할 수 있으며, 많은
then
과catch
를 사용하면 코드가 길어질 수 있습니다.
3) async/await
(1) 개념
async/await
는 Promise를 더 간결하고 동기적으로 보이게 작성할 수 있는 문법입니다. async
키워드는 함수 앞에 붙여 해당 함수가 Promise를 반환하도록 하고, await
키워드는 Promise가 해결될 때까지 기다립니다. await
키워드는 async
함수 내에서만 사용할 수 있습니다.
(2) 실행 순서
async
함수가 호출됩니다.- 함수 내에서
await
키워드로 비동기 작업을 기다립니다. - 비동기 작업이 완료되면 다음 코드가 실행됩니다.
(3) 예제 코드
function goMart() {
console.log('마트에 가서 어떤 음료를 살지 고민한다..');
}
function pickDrink() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('고민 끝');
const product = '제로콜라';
const price = 3000;
resolve({ product, price });
}, 3000);
});
}
function pay({ product, price }) {
console.log(`상품명: ${product} // 가격: ${price}`);
}
async function exec() {
goMart();
try {
const result = await pickDrink();
pay(result);
} catch (err) {
console.error(err);
}
}
exec();
// 아래 코드처럼 더 직관적으로 작성도 가능함.
// async function exec() {
// try {
// goMart();
// await pickDrink();
// pay();
// }
// catch(err) {
// console.error(err);
// }
// }
// exec();
(4) 분석
exec
함수는async
키워드로 정의되어 있습니다. 이 함수는 Promise를 반환하는 비동기 함수가 됩니다.exec
함수가 호출되면 먼저goMart
함수가 실행되어 “마트에 가서 어떤 음료를 살지 고민한다..”라는 메시지를 출력합니다.await
키워드가pickDrink
함수 앞에 붙어 있으므로 이 함수가 반환하는 Promise가 해결될 때까지 기다립니다. 3초 후 “고민 끝”이라는 메시지가 출력되고,product
와price
가 설정된 객체가 반환됩니다.await
뒤의 Promise가 해결되면pay
함수가 호출되어 “상품명: 제로콜라 // 가격: 3000”이라는 메시지를 출력합니다.try...catch
블록을 사용하여 비동기 작업 중 발생할 수 있는 에러를 처리합니다. 이 예제에서는reject
가 호출되지 않으므로catch
블록은 실행되지 않습니다.
(5) 장단점
- 장점: 가독성이 매우 좋고, 동기 코드처럼 작성할 수 있습니다. 비동기 작업의 흐름을 쉽게 이해할 수 있습니다.
- 단점: 최신 문법이므로 오래된 브라우저에서는 지원되지 않을 수 있습니다.