비동기 처리는 ‘특정 코드 실행이 완료될 때까지 기다리지 않고, 다음 코드를 먼저 처리하는 방식’을 말한다. 이러한 비동기 처리를 위해 Javascript에서는 콜백 함수를 하나의 패턴으로 사용하기도 하는데, 콜백 헬로 인해 가독성이 떨어지거나 에러 처리가 곤란해지는 문제가 생기기도 한다. 이런 문제점을 보완하기 위해 ES6에서는 '프로미스(Promise)'를 도입했다. 프로미스는 기존의 콜백 패턴의 단점을 보완하고 비동기 처리 시점을 명확히 표현할 수 있다는 장점이 있어 유용하다. 일단 콜백 패턴을 먼저 살펴보며 어떤 단점이 있는지 살펴보고, Promise를 통해 보완하는 과정을 살펴보자.
1. 콜백 패턴
일반적으로 콜백함수를 전달하는 방식으로 비동기 처리를 한 예시이다. 필요에 따라 비동기 처리에 성공할 때, 그리고 실패할 때 호출될 콜백 함수를 전달할 수 있다. 이런 패턴은 어떤 문제점이 있을까?
문제점1. 콜백함수 호출의 중첩 (Callback Hell)
let printStr = (str, callback) => {
setTimeout(() => {
console.log(str);
callback();
}, 500)
}
let printAll = () => {
printStr('1', () => {
printStr('2', () => {
printStr('3', () => {
//...
})
})
})
}
printAll();
문제점2. 에러 처리의 한계
let func = callback => {
if (err) {
callback(err, null);
}
else {
callback(null, data);
}
}
2. Promise Constructor
Promise 생성자 함수를 new 키워드로 호출하면 Promise 객체를 생성한다. (표준 빌트인 객체) Promise 생성자 함수는 비동기 처리를 수행할 콜백 함수를 매개변수로 전달받는데, 이 콜백함수는 resolve, reject 함수를 매개변수로 전달받는다.
Promise 생성자 함수가 전달받은 콜백함수 내부에서 비동기 처리를 수행하는데, 이 처리가 성공하면 처리 결과를 resolve 함수의 매개변수로 전달하며 호출하고, 실패하면 에러를 reject 함수에 전달하면서 호출한다.
let promise = new Promise((resolve, reject) => {
if (성공) {
resolve('성공');
} else {
reject('실패');
}
})
Promise의 상태 정보
new Promise()로 프로미스를 생성하고 종료될 때까지 프로미스는 총 세가지 상태를 갖는다. (pending, fulfilled, rejected)
1. pending : 비동기 처리 미완료 → 프로미스 생성 직후 (기본 상태)
2. fulfilled : 비동기 처리 성공 → resolve() 호출
let fulfilled = new Promise(resolve => resolve(1));
3. rejected : 비동기 처리 실패, 오류발생 → reject() 호출
let rejected = new Promise((_, reject) => reject(new Error('에러')));
Promise 후속 처리 메서드
프로미스의 비동기 처리 상태에 따라 후속처리를 해야하는데, 예를 들어 fulfilled 상태에는 처리결과로 무언가를 수행해야 하고 rejected 상태면 에러를 가지고 어떤 처리를 해야한다. 프로미스는 이런 후속 처리를 위해 then, catch, finally 메서드를 사용한다. 셋 모두 언제나 Promise 객체를 반환한다.
1. then
[인자1] 성공처리 콜백함수 (fulfilled 상태에 호출됨) → 비동기처리 결과를 전달 받음
[인자2] 실패처리 콜백함수 (rejected 상태에 호출됨) → 에러를 전달 받음
then의 콜백함수가 프로미스를 반환하는 경우에는 프로미스 그대로 반환하고, 프로미스 아닌 것을 반환하는 경우에는 암묵적으로 resolve, reject를 통해 프로미스를 생성해 반환한다. 결국 언제나 프로미스 객체를 반환한다!
new Promise(resolve => resolve('성공'))
.then(val => console.log(val), err => console.error(err)); // 성공
new Promise((_, reject) => reject(new Error('에러')))
.then(val => console.log(val), err => console.error(err)); // Error: 에러
2. catch
catch 메서드는 then과 달리 한 개의 콜백함수만 인자로 전달받는다. rejected 상태인 경우에만 호출되는 실패처리 콜백함수가 그 유일한 인자인데, 에러가 발생하지 않으면 아예 호출이 되지 않는다. 따라서 catch 메서드는 에러처리를 할 때 사용한다.
+ 에러를 처리하는 방법은 1) then의 두번째 인자로 처리하거나, 2) catch를 이용하는 두 가지 방법이 있다. 하지만 catch가 더 많은 예외를 처리하기 때문에 가급적이면 catch를 사용할 것을 권장한다고 한다.
new Promise((_, reject) => reject(new Error('에러')))
.catch(err => console.log(err)) // Error: 에러
3. finally
finally 메서드는 성공, 실패 상관없이 무조건 한 번 호출되는데, 이런 특성 덕분에 상태에 상관없이 공통으로 수행해야 할 처리 내용이 있을 때 유용하게 쓰인다.
new Promise(() => {})
.finally(() => console.log('마지막')); // 마지막
'Language > Javascript' 카테고리의 다른 글
이벤트 루프 (setTimeout의 시간은 정확할까?) (0) | 2021.06.07 |
---|---|
Promise (2) 프로미스 체이닝, Promise.all, Async/Await (0) | 2021.02.02 |
단축평가, 옵셔널체이닝연산자, null병합연산자 (ES11) (0) | 2021.01.29 |
암묵적, 명시적 타입 변환 (0) | 2021.01.29 |
유용하지만 위험한 화살표함수(=>) (0) | 2021.01.16 |
댓글