1. 정의
일반적으로 코드는 순차적으로 실행되며, 완료가 되어야 다음으로 넘어간다.
차례대로 하나하나씩 실행하는 것을 동기라고 이해할 수 있다.
그렇다면 비동기는?
비동기란 말 그대로 동기가 아니라는 의미.
어떤 작업의 완료를 기다리지 않고 다음 작업으로 진행하는 것을 말한다.
따라서 여러 작업을 병렬로 수행시킬 수도 있다.
병렬로 처리한다는 부분에서 슈퍼스칼라 아키텍처와 유사한 점이 있다.
다음의 이미지를 참고할 수 있겠다.
2. Promise
JS에서의 비동기 처리를 위해 ``Promise``는 꼭 알아야 하는 개념이다.
``Promise``는 JS에서 비동기 작업을 처리하기 위한 객체로,
비동기 작업의 성공(``resolved``) 또는 실패(``rejected``)를 처리할 수 있는 방법을 제공한다.
``Promise``는 비동기 작업의 결과를 기다렸다가, 그 결과에 따라 다른 작업을 실행할 수 있게 한다.
A. Promise의 기본 개념
Promise는 다음의 세 가지 상태를 가진다.
- Pending (대기): 비동기 작업이 아직 완료되지 않은 상태.
- Fulfilled (이행됨): 비동기 작업이 성공적으로 완료된 상태.
- Rejected (거부됨): 비동기 작업이 실패한 상태.
B. 생성
주로 다음과 같은 형태로 만들어진다.
const myPromise = new Promise((resolve, reject) => {
// 비동기 작업을 수행
const success = true; // 예시로 성공 여부를 판단
if (success) {
resolve('작업이 성공적으로 완료되었습니다.'); // 작업 성공 시
} else {
reject('작업이 실패했습니다.'); // 작업 실패 시
}
});
``Promise``는 생성될 때, 함수(주로 콜백 함수) 하나를 인수로 받는다.
이 함수는 두 개의 인자를 갖는데, ``resolve``와 ``reject`` 2가지이다.
``resolve``는 작업이 성공적으로 완료되었을 때 호출되며, ``reject``는 작업이 실패했을 때 호출된다.
C. 사용
비동기 작업의 결과가 나오면 다음과 같이 처리한다.
myPromise
.then(result => {
console.log(result); // 작업 성공 시 실행될 코드
})
.catch(error => {
console.error(error); // 작업 실패 시 실행될 코드
});
- .then(): ``Promise``가 ``resolve``되었을 때 실행될 콜백을 정의.
- 이는 성공 결과를 받는다. - .catch(): ``Promise``가 ``reject``되었을 때 실행될 콜백정의.
- 이는 실패 이유를 받는다.
D. 예시
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { name: 'JS', type: 'Lang' };
const success = true; // 이 부분을 false로 바꾸면 에러 발생
if (success) {
resolve(data); // 작업 성공 시 데이터 전달
} else {
reject('데이터를 가져오는 데 실패했습니다.'); // 작업 실패 시 에러 메시지 전달
}
}, 1000); // 1초 후에 실행
});
}
fetchData()
.then(data => {
console.log('받은 데이터:', data);
})
.catch(error => {
console.error('에러 발생:', error);
});
여기선 ``fetchData``가 ``Promise``를 리턴한다.
1초 후에 성공하면 ``resolve``, 아니면 ``reject``된다.
여기선 ``then()``과 ``catch()``를 통해 각 분기 별 처리를 정의했다.
E. 체이닝
``then()``은 다른 ``Promise``를 리턴할 수 있으므로,
여러 비동기 작업을 동기적으로 처리하는 데 유용하게 사용할 수 있다.
fetchData()
.then(data => {
console.log('첫 번째 데이터:', data);
return fetchMoreData(data); // 두 번째 비동기 작업 반환
})
.then(moreData => {
console.log('두 번째 데이터:', moreData);
})
.catch(error => {
console.error('에러 발생:', error);
});
이렇게 비동기를 동기처럼 순차적으로 처리하게 할 수 있다.
가독성의 제고 또한 기대할 수 있다.
이렇게 ``Promise``를 사용하여 콜백 헬을 피할 수 있고,
보다 작성하기 쉽고 유지보수에 용이한 코드를 작성할 수 있다.
3. Async / Await
``Promise``와 같이 많이 사용하게 될 개념이 ``Async``와 ``Await``다.
이전보다도 코드를 더 간결하고 덜 헷갈리게 작성할 수 있게 해 준다.
A. Async/Await의 기본 개념
- async 함수: 함수 앞에 ``async`` 키워드를 붙이면, 해당 함수는 항상 ``Promise``를 반환한다.
- 함수 내부에서 리턴한 값은 자동으로 ``Promise.resolve``로 감싸진다. - await 키워드: ``Promise``가 해결될 때까지 기다렸다가 그 결과를 반환한다.
- ``await``는 ``async`` 함수 내부에서만 사용할 수 있으며,
- ``await`` 키워드는 ``Promise``가 해결될 때까지 함수의 실행을 일시 중지하고, 그 이후에 실행을 재개한다.
B. Async
async function fetchData() {
return '데이터';
}
fetchData().then(data => console.log(data)); // "데이터" 출력
``fetchData`` 함수는 ``async``로 선언되었기 때문에, 자동으로 ``Promise``를 반환한다.
``return '데이터';``는 ``Promise.resolve('데이터')``와 동일한 결과를 나타낸다.
C. Await
``await``를 사용해 비동기를 동기처럼 돌아가게 할 수 있다.
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function fetchData() {
console.log('데이터를 가져오는 중...');
await delay(2000); // 2초 대기
return '데이터';
}
async function process() {
const data = await fetchData(); // fetchData의 결과를 기다림
console.log('받은 데이터:', data);
}
process();
``process``함수는 ``await``로 인해 ``fetchdata()``가 완료될 때까지 대기한다.
기다리는 함수가 완료되면 그 결과를 ``data`에 저장하게 된다.
종합하여 아래와 같은 형태로 만들 수 있다.
var addCoffee = function (name) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(name);
}, 500);
});
};
var coffeeMaker = async function () {
var coffeeList = '';
var _addCoffee = async function (name) {
coffeeList += (coffeeList ? ', ' : '') + await addCoffee(name);
};
await _addCoffee('에스프레소');
console.log(coffeeList);
await _addCoffee('아메리카노');
console.log(coffeeList);
await _addCoffee('카페모카');
console.log(coffeeList);
await _addCoffee('카페라떼');
console.log(coffeeList);
};
coffeeMaker();
이런 식으로 비동기 작업도 동기처럼 활용할 수 있다.
D. 예외 처리
``Promise``만 사용할 땐 ``then()``과 ``catch()``를 사용했는데,
이젠 ``try()``와 ``catch()``를 사용한다.
async function fetchDataWithError() {
throw new Error('데이터를 가져오는 데 실패했습니다.');
}
async function process() {
try {
const data = await fetchDataWithError();
console.log('받은 데이터:', data);
} catch (error) {
console.error('에러 발생:', error.message);
}
}
process();
``then()``를 사용하지 말라는 법은 없다.
하지만 ``try / catch``가 권장되는 이유는 가독성과 일관성에 있다.
``try / catch``는 동기 코드에서 사용되는 것과 동일한 형태로 예외 처리를 수행할 수 있다.
보다 직관적이기 때문에 이해하기도 쉽다.
E. 병렬 처리
``Promise.all``을 사용하여 여러 비동기 작업을 병렬로 처리할 수 있다.
async function fetchData1() {
return '데이터 1';
}
async function fetchData2() {
return '데이터 2';
}
async function process() {
const [data1, data2] = await Promise.all([fetchData1(), fetchData2()]);
console.log('받은 데이터:', data1, data2);
}
process();
``fetchData1/2``가 병렬로 실행되며, 두 작업이 모두 완료될 때 까지 기다린 후 데이터를 리턴한다.
4. 결론
``Async / Await``는 JS의 비동기 작업을 처리하는 데 있어 강력하고 직관적인 방법이다.
가독성과 유지보수성의 제고를 대단히 기대할 수 있다.
비동기 작업이 매우 많이 일어나는 환경인 서버 사이드에서 매우 유용히 사용되기 때문에,
지속적으로 개념을 잊지 않도록 공부하는 것이 좋겠다.
'Camp > T.I.L.' 카테고리의 다른 글
[TIL #6] 햄버거 만들기 (0) | 2024.08.23 |
---|---|
[TIL #5] 소수 만들기 (0) | 2024.08.22 |
[TIL #3] this, call(), apply(), bind() (0) | 2024.08.12 |
[KPT 회고] 팀 소개 페이지 미니 프로젝트에 대한 회고 (0) | 2024.08.09 |
[TIL #2] 간단한 API 프록시 서버 구현 (0) | 2024.08.09 |