Promise의 개념
프로미스는 비동기를 위한 오브젝트 중 하나로
pre-enrollment service와 after-enrollment service가 작동하는 방식에 비유될 수 있다.
예를 들어 원하는 코스가 아직 개강 전일 때 접수했다면 개강 이후 자동 접수가 되고
개강일이 지난 후에 접수하면 그대로 접수가 되는 방식이다.
프로미스는 Pending과 (fulfilled/rejected)라는 두 가지 상태를 가지고 있으며 원하는 기능을 수행해서 해당하는 데이터를 만들어내는 Producer와 원하는 데이터를 소비하는 Consumer를 이용하여 비동기적으로 데이터를 처리할 수 있다.
Promise의 Producer
Producer를 만드는 과정은 다음과 같다.
프로미스는 생성될 때 우리가 작성한 executor( (resolve, reject) => { ...})가 바로 실행된다.
!따라서 사용자 요구에 따라 실행되어야한다면 아래와 같이 작성하면 안 된다는 점을 알아두어야 한다!
const promise = new Promise((resolve, reject)=>{
//doing some heavy work (network, read files)
console.log('doing something....')
setTimeout(() => {
resolve('ellie'); // SUCESS - 성공적으로 네트워크, 파일 등에서 받아온 것을 resolve에 전달함
//reject(new Error('no network'));// Error handling
}, 2000);
}); // immediately executed -> can be unnecessary
Promise의 Consumer
이후 Consumer에서 전달한 값을 사용하게 되는데 resolve에서 전달받은 값이 then의 value가 되고,
반대로 reject에 전달된 값은 catch에 전달된다. finally는 then과 catch에 상관없이 실행된다.
.then .catch 모두 array api처럼 해당 Promise 자체를 리턴할 수 있기 때문에 아래처럼 chain 형식으로 실행 가능하다.
// 2. Consumer; then, catch, finally
// then <- resolve
promise
.then((value) => {
console.log(value);
})
.catch(error => {
console.log(error);
})
.finally(() => console.log('finally'))
Promise의 Chaining
이를 활용하면 아래처럼도 사용할 수 있다.
setTImeout부분을 서버통신이라고 생각하면
서버통신으로 가져온 값들을 연산하고 그 값을 다른 서버와 통신 한 후에 값을 가져오는 코드라고 볼 수 있다. 모두 then이 결과값 뿐만 아니라 Promise를 리턴할 수 있기 때문에 할 수 있는 일들이다.
const fetchNumber = new Promise((resolve, reject)=>{
setTimeout(() => resolve(1), 1000)
});
fetchNumber
.then(num => num * 2) // result fetchNumber promise
.then(num => num * 3) // result fetchNumber promise
.then(num => { // result another Promise
return new Promise((resolve, reject)=>{
setTimeout(() => {
resolve(num - 1)
}, 1000);
});
})
.then(num => console.log(num))
Error Handling
const getHen = ()=>
new Promise((resolve, reject)=>{
setTimeout(() => {resolve('🐔')}, 1000);
})
const getEgg = hen =>
new Promise((resolve, reject)=> {
setTimeout(() =>
resolve(`${hen} => 🥚`); }, 1000);
//reject(new Error('error!')), 1000);
})
const cook = egg =>
new Promise((resolve, reject)=>{
setTimeout(() => { resolve(`${egg} =>🍳`); }, 1000)
})
getHen()
.then(getEgg)// (hen) = > getEgg(hen) 암묵적 전달
.then(cook)
.then(console.log) // (meal) = > console.log(meal) 암묵적 전달
이 경우 🐔 => 🥚 => 🍳 순으로 잘 출력된다ㅏ
하지만 만약에 중간에 하나의 프로미스에서 에러가 발생하면 어떻게 될까?
getEgg에서 서버통신 과정에서 오류가 발생했다고 가정해보자
const getHen = ()=>
new Promise((resolve, reject)=>{
setTimeout(() => {resolve('🐔')}, 1000);
})
const getEgg = hen =>
new Promise((resolve, reject)=> {
setTimeout(() =>
//resolve(`${hen} => 🥚`); }, 1000);
reject(new Error('error!')), 1000);
})
const cook = egg =>
new Promise((resolve, reject)=>{
setTimeout(() => { resolve(`${egg} =>🍳`); }, 1000)
})
getHen()
.then(getEgg) // (hen) = > getEgg(hen) 암묵적 전달
.then(cook)
.then((meal) => console.log(meal))
.catch(console.log)
이때 catch의 위치가 중요하다. (물론 catch 안 쓰면 걍 에러뜸)
catch 위치에 따라 에러를 잡는 위치가 달라져서 다른 Promise Chain의 실행을 완료할지 말지가 정해지기 때문이다.
위와 같이 catch 처리를 하게 되면 에러 메세지만 보이고 다른 것들은 아무것도 뜨지 않게 된다.
만약 getEgg에서 에러가 나면 다른 데이터로 대체하여 사용하든가 해서
나머지 promise chain에 영향을 주고 싶지 않다면 .then(getEgg) 바로 뒤에서 catch 해줘야한다.
getHen()
.then(getEgg) // (hen) = > getEgg(hen) 암묵적 전달
.catch(() => {return '🍞'})
.then(cook)
.then((meal) => console.log(meal))
.catch(console.log)
출력값: 🍞 =>🍳
이 경우 달걀을 가져오는 데에는 실패했지만 무사히 cook를 마칠 수 있다.
Callback to Promise
class UserStorage{
loginUser(id, password, onSuccess, onError){
setTimeout(()=> {
if(
(id === 'ellie' && password === 'dream') ||
(id === 'coder' && password === 'academy')
){
onSuccess(id);
}else{
onError(new Error('not found'))
}
}, 2000)
}
getRoles(user, onSuccess, onError){
setTimeout(() => {
if( user === 'ellie'){
onSuccess({name: 'ellie', role: 'admin '});
}else{
onError(new Error('no access'));
}
}, 1000);
}
}
* 콜백으로 구현한 UserStorage
먼저 UserStorage 클래스부터 Promise를 활용하여 바꾸어준다.
콜백으로 계속해서 이어 호출 되는 것이 아니라 Promise를 리턴해주는 것으로 변했기 때문에 더 깔끔해졌다.
class UserStorage {
loginUser(id, password) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (
(id === "ellie" && password === "dream") ||
(id === "coder" && password === "academy")
) {
resolve(id);
} else {
reject(new Error("not found"));
}
}, 2000);
});
}
getRoles(user) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (user === "ellie") {
resolve({ name: "ellie", role: "admin " });
} else {
reject(new Error("no access"));
}
}, 1000);
});
}
}
const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password')
userStorage
.loginUser(id, password)
.then(userStorage.getRoles)
.then((user) => console.log(`${user.name} has ${user.role}role`));
'javascript' 카테고리의 다른 글
[함수형 자바스크립트] 객체의 불변성을 관리하는 방법들 (0) | 2020.11.30 |
---|---|
[함수형 언어, 자바스크립트] 비동기 처리 (3) await과 async의 이해 (0) | 2020.11.19 |
[함수형 언어, 자바스크립트] 비동기 처리 (1) Callback 함수의 이해 (0) | 2020.11.17 |
[함수형 언어, 자바스크립트] 배열에 함수 체이닝 (1) (0) | 2020.10.22 |
[함수형 언어, 자바스크립트] 배열 (0) | 2020.10.22 |