react를 공부하다가 async와 await를 만났다.
api를 가져와서 state에 json을 담아주는 과정에서 사용한 거였다.
어떻게 사용하는지는 대강 이해가 가는데, 구글링을 하다보니 내가 모르는 promise라는 개념과 많이 엮여있는 거 같아서 한 번 정리를 하고 넘어가려고 한다.
해당 정리는 유투브 드림코딩 채널의 엘리님 강의를 참고하였다.
promise는 js에서 제공하는 object이다.
비동기를 간단하게 처리할 수 있도록 도와주는 역할을 한다.
간단히 설명하면 이러하다.
정해진 장시간의 기능을 수행하고나서, 성공적으로 수행되었다면 처리된 결과값을 전달한다.
수행에 문제가 발생했다면 에러를 띄운다.
promise는 callback 대신 사용하는 object이다.
promise의 핵심은 세 가지다.
state
요청을 수행 중인 상태 : pending
요청을 수행 완료한 상태 : fulfilled
뜻밖의 문제로 요청을 수행하지 못한 상태 : rejected
producer
원하는 기능을 수행해서 만들어내는 역할 (처음 선언된 promise)
consumer
원하는 데이터를 소비하는 역할 (callback의 역할을 하는 부분)
promise object 기본형태
const promise = new Promise((resolve, reject) => {
//시간이 오래 걸리는 작업
});
시간이 걸리는 작업은 응답이 언제 올지 알 수가 없다.
때문에 동기로 처리가 되면 다음 코드들이 기약없이 실행되지 않게 된다.
따라서 반드시 비동기로 처리 해줘야한다.
const promise = new Promise((resolve, reject) => {
//시간이 오래 걸리는 작업
console.log("doing something...");
});
콘솔에 아무 문자열을 적어서 띄워봤다.
콘솔에 잘 뜬다.
여기서 알 수 있는 사실!
promise를 사용한 순간 코드의 수행이 이루어진다.
만약에 promise로 네트워크 통신을 한다면, promise가 사용되는 순간 네트워크 통신이 이루어지는 것이다.
네트워크 요청을 사용자가 원하는 시점에 해야하는 거였다면, 불필요한 통신이 이루어지게 된 것이다.
기억하자.
새로운 promise가 만들어지면 promise 안에서 작성한 코드는 바로 실행이 된다.
const promise = new Promise((resolve, reject) => {
//시간이 오래 걸리는 작업
console.log("doing something...");
setTimeout(() => {
resolve("hyobbang");
}, 2000);
});
2초 뒤에 hyobbang 이라는 결과값을 전달해주는 callback을 실행하는 promise 를 만들어봤다.
위처럼 값을 받아오는 역할을 하는 promise가 작성이 되었다면,
이제 이것을 이용하는 consumer를 만들 차례다.
promise.then((value) => {
console.log(value);
});
해당 코드는 resolve가 반환해주는 값을 .then 문법을 써서 value로 받고,
이것을 콘솔에 찍어보는 기능이다.
예상대로 2초 후에 콘솔에 hyobbang이 찍혔다.
그럼 이번엔 reject를 핸들링해보자.
const promise = new Promise((resolve, reject) => {
//시간이 오래 걸리는 작업
console.log("doing something...");
setTimeout(() => {
//resolve("hyobbang");
reject(new Error("no network"));
}, 2000);
});
promise
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error);
});
resolve 함수를 주석처리 하고 reject를 만들어준 후에 .catch를 써주면
이렇게 선언한 문자열이 나오며 에러메세지를 띄운다.
에러의 유무와 상관없이 무조건 동작하게 만드는 방법도 있다.
const promise = new Promise((resolve, reject) => {
//시간이 오래 걸리는 작업
console.log("doing something...");
setTimeout(() => {
resolve("hyobbang");
reject(new Error("no network"));
}, 2000);
});
promise
.then((value) => {
console.log(value);
})
.catch((error) => {
console.log(error);
})
.finally(() => {
console.log("finally");
});
finally를 선언하고 아무 인자도 넣지 않은 채로 콘솔에 finally를 찍어보면
이렇게 resolve의 값과 함께 찍히게 된다.
참고로 이건 java에서의 에러 핸들링과 거의 흡사해서 이해하기 쉬웠다.
const fetchNumber = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000);
});
fetchNumber
.then((num) => num * 2)
.then((num) => num * 3)
.then((num) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(num - 1), 1000);
});
})
.then((num) => console.log(num));
chaining은 이름 그대로 여러 함수를 연결해서 사용하는 것을 뜻한다.
여기서는 .then()을 계속 연결해서 사용해주었다.
fetchNumber라는 promise를 만들어 준 뒤에 2를 곱하고, 3을 곱하고,
새로운 promise에 다시 넣고 -1을 해준 뒤 콘솔에 찍어보는 소스다.
예상대로 2초 후 5가 출력되었다.
여기서 중요한 사실!
.then()은 promise도 전달한다.
callback 포스팅에서 작성했던 코드를 promise를 사용해 깔끔하게 리팩토링 해보도록 하자.
class UserStorage {
loginUser(id, password, onSuccess, onError) {
setTimeout(() => {
if (
(id === 'hyobbang' && password === 'great') ||
(id === 'coder' && password === 'academy')
) {
onSuccess(id);
} else {
onError(new Error('not found'));
}
}, 2000);
}
getRoles(user, onSuccess, onError) {
setTimeout(() => {
if (user === 'hyobbang') {
onSuccess({ name: 'hyobbang', role: 'admin' });
} else {
onError(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,
user => { //loginUser 성공시
userStorage.getRoles(
user,
userWithRole => { //getRoles성공
alert( //로그인이 잘 됐다는 메세지 출력
`Hello ${userWithRole.name}, you have a ${userWithRole.role} role`
);
},
error => { //getRoles실패
console.log(error);
}
);
},
error => { //loginUser 실패시
console.log(error);
}
);
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) // (1)로그인 성공하면
.then(userStorage.getRoles) // (2)를 수행, 성공하면
.then(user => alert( // (3)을 수행 - 로그인이 잘 됐다는 메세지 출력
`Hello ${user.name}, you have a ${user.role} role`))
.catch(error => alert('error')); // error 대응
loginUser와 getRoles가 이중으로 구성되어있던 콜백지옥에서
promise의 사용으로 깔끔하게 분리된 것을 볼 수 있다.
특히나 .then을 사용하여 getRoles가 성공했을 시에 alert를 띄우고, 실패했을 시에 error 메세지를 띄우는 부분이 좀 더 알아보기 쉽게 정리되었다.