ES6+ 향상된 비동기 프로그래밍(1) - 프로미스
ES6에서 Promise가 javascript 언어에 포함이 되었습니다.
- 이제 콜백 지옥에서 벗어나 보자.
1. 프로미스에 대해 알아보자.
#1 Promise(프로미스) 간단 사용법
아래의 코드는 Promise를 사용해 순차적으로 비동기 프로그래밍을 작성한것이다.
getData()
.then(data => {
console.log(data);
return getData2();
})
.then(data => {
console.log(data);
//.....
});
콜백 함수를 찾아 다닐 필요 없이 순차적으로 작성할수 있어 직관적이다.
프로미스의 세 가지 상태
- pending(대기중) : 결과를 기다리는 중
- fulfilled(이행됨) : 수행이 정상적로 끝났고 결괏값을 가지고 있음
- rejected(거부됨) : 수행이 비정상적으로 끝났음
fulfilled(이행됨), rejected(거부됨) 상태를 settled(처리됨) 상태라고 한다.
프로미스는 settled(처리됨) 상태가 되면 더 이상 다른 상태로 변경되지 않는다.
pending(대기중) 상태일 때만 fulfilled(이행됨) 또는 rejected(거부됨) 상태로 변할 수 있다.
프로미스는를 생성 하는 세 가지 방법
const promise1 = new Promise((resolve, reject) => {
//...
// resolve(data);
// or reject('error message');
});
const promise2 = Promise.reject('error message');
const promise3 = Promise.resolve(param);
promise1은 new 키워드를 사용해서 프로미스를 생성한다.
이때 생성된 프로미스는 대기 중 상태가 된다.
생성자의 매개변수로 resolve와 reject라는 콜백함수를 입력할 수 있다.
비동기로 어떤 작업을 수행하고 성공했을 때 resolve를 호출하고 promise1 객체는 fulfilled(이행됨)
실패 시 reject가 호출되고 promise1 객체는 rejected(거부됨) 상태가 된다.
new 키워드를 사용해 Promise를 생성할 때 입력 된 함수가 만약 API를 요청하는 비동기 코드이면 생성되는 순간 요청을 보낸다.
생성 시 입력된 함수에 예외(exception)가 발생하면 rejected(거부됨) 상태가 된다.
promise2 : new 키워드를 사용하지 않고 Promise.reject를 호출하면 rejected(거부됨) 상태의 프로미스가 생성된다.
promise3 : Promise.resolve를 호출해도 프로미스가 생성된다. 이때 입력값이 프로미스였다면 그 객체가 그대로 반환되고, 프로미스가 아니면 fulfilled(이행됨) 상태인 프로미스가 반환된다.
다음은 Promise.resolve의 인수가 프로미스인 경우와 아닌 경우의 반환값을 보여준다.
const promise1 = Promise.resolve(1);
console.log(promise1 !== 1); // true
const promise2 = new Promise(resolve => setTimeout(() => {
resolve(10);
}, 1));
console.log(Promise.resolve(promise2) === promise2) // true
promise1 : 프로미스가 아닌 인수와 함께 Promise.resolve 함수를 호출하면 그 값 그대로 fulfilled(이행됨) 상태인 프로미스가 반환된다. promise1은 1을 데이터로 가진 프로미스다.
promise2 : Promise.resolve 함수에 프로미스가 입력되면 그 자신이 반환된다.
#2 Promise(프로미스) 이용하기 1편 : then
settled(처리됨) 상태가 된 프로미스를 처리할 때 then 메서드를 사용한다.
프로미스가 settled(처리됨) 상태가 되면 then 메서드의 인수로 전달된 함수가 호출된다.
다음은 then 메서드의 사용법이다.
requestApi().then(onResolve, onReject); // 1번
Promise.resolve(123).then(data => console.log(data)); // 123
Promise.reject('error').then(null, err => console.log(err)); // 에러 발생
1번 : 프로미스가 settled(처리됨) 상태가 되면 onResolve 함수가 호출되고, rejected(거부됨) 상태가 되면 onReject 함수가 호출된다.
then 메서드는 항상 프로미스를 반환한다. 따라서 하나의 프로미스로부터 연속적으로 then 메서드를 호출할 수 있다.
requestApi1()
.then(data => {
console.log(data);
return requestApi2(); // 1번
})
.then(data => {
return data + 1; // 2번
})
.then(data => {
throw new Error('something is wrong.');// 3번
})
.then(null, error => {
console.log(error);
});
1번 : onResolve 또는 onReject 함수에서 프로미스를 반환하면 then 메서드는 그 값을 그대로 반환한다.
2번 : 만약 프로미스가 아닌 값을 반환하면 then 메서드는 fulfilled(이행됨) 상태인 프로미스를 반환한다.
3번 : onResolve 또는 onReject 함수 내부에서 예외가 발생하면 then 메서드는 rejected(거부됨) 상태인 프로미스를 반환한다.
결과적으로 then 메서드는 항상 프로미스를 반환한다.
프로미스가 rejected(거부됨) 상태인 경우에는 onReject 함수가 존재하는 then을 만날때까지 이동한다.
Promise.reject('err')
.then(() => console.log('then 1')) // 1
.then(() => console.log('then 1')) // 번
.then(() => console.log('then 3'), () => console.log('then 4')) // 2번
.then(() => console.log('then 5'), () => console.log('then 6'));// 3번
결과
then 4
then 5
Promise {<fulfilled>: undefined}
rejected(거부됨) 상태인 프로미스는 처음으로 만나는 onReject 함수를 호출하므로 1번은 생략
2번 : then 4 출력하는 onReject 함수는 undefined를 결과로 가지면서 fulfilled(이행됨) 상태인 프로미스를 생성한다.
3번 : 따라서 then 5가 출력 된다.
then 메서드의 가장 중요한 특징은 항상 연결된 순서대로 호출된다는 점이다. 이 특징 덕에 프로미스로 비동기 프로그래밍을 할 때 동기 프로그래밍 방식으로 코드를 작성할 수 있다.
#2 Promise(프로미스) 이용하기 2편 : catch
catch : 예외를 처리하는 메서드 ( then 메서드의 onReject 함수와 같은 역할 )
같은 기능을 하는 then 메서드와 catch 메서드
Promise.reject(1).then(null, error => {
console.log(error);
});
Promise.reject(1).catch(error => {
console.log(error);
});
예외 처리는 then 메서드의 onReject 함수보다는 catch 메서드를 이용하는 게 가독성 면에서 더 좋다.
then + onReject 문제점
Promise.resolve().then(
() => {
throw new Error('Something is wrong!');
},
error => {
console.log(error);
}
);
then 메서드의 onResolve 함수에서 발생한 예외는 같은 then 메서드의 onReject 함수에서 처리되지 않는다.
Chrome console에서는 Uncaught (in promise) Error: Something is wrong! 라는 에러가 발생한다.
rejected(거부됨) 상태의 프로미스를 처리되지 않았기 때문이다.
다음과 같이 수정하면 이 문제는 해결된다.
Promise.resolve()
.then(() => {
throw new Error('Something is wrong!');
})
.catch(error => {
console.log(error);
});
이 처럼 catch를 사용해 직관적인 코드를 작성하자.
catch 메서드도 then 메서드 처럼 프로미스를 반환하기 때문에 프로미스 chain을 만들수 있다.
Promise.reject(1)
.then(data => {
console.log('then1: ', data);
return 2;
})
.catch(error => {
console.log('catch: ', error);
return 3;
})
.then(data => {
console.log('then2: ', data);
});
// catch: 2
// then2: 3
#2 Promise(프로미스) 이용하기 3편 : finally
finally는 프로미스가 fulfilled(이행됨) 또는 rejected(거부됨) 상태일 때 호출되는 메서드다.
2018년에 javascript 표준으로 채택됐다.
finally 메서드는 다음과 같이 프로미스 체인의 가장 마지막에 사용된다.
requestData()
.then(data => {
//..
})
.catch(error => {
//..
})
.finally(() => {
//..
});
finally 메서드는 .then( onFinally, onFinally ) 코드와 유사하지만, 이전에 사용된 프로미스를 그대로 반환한다는 점이 다르다.
따라서 settled(처리됨) 상태인 프로미스의 데이터를 건드리지 않고 추가 작업을 할 때 유용하다.
아래는 데이터 요청의 성공, 실패 여부와 상관없이 서버에 로그를 보낼 때 finally메서드를 사용한 코드다.
function requestData(){
return fetch()
.catch(error => {
//..
})
.finally(() => {
sendLog('log');
});
}
requestData().then(data => console.log(data)); // 1번
1번의 data는 finally 메서드가 호출 되기 이전의 프로미스다.
requestData 함수를 사용하는 입장에서는 finally 메서드의 존재 여부를 신경 쓰지 않아도 된다.
다음글 [React] - ES6+ 향상된 비동기 프로그래밍(2) - 프로미스 활용