JavaScript promise 개념과 활용에 대해 공부함
비유를 하자면 학원에 강의 코스가 하나 있는데 언제 이 코스가 완성될지 모르는 상황이다. 관심있는 학생이라면 이메일을 통해 미리 등록할 수 있는 시스템이 있다고 가정해볼때
학생A가 이메일을 보내서 미리 등록했다면 몇시간 또는 며칠이 지난 다음 코스가 오픈되었을 때 바로 학생A에게 문자로 알림이 갈 것이다.(promise가 성공적으로 값을 전달)
학생A는 수업이 준비되기 전에 미리 등록을 해놨기 때문에 수업이 완성되자마자 공지를 받을 수 있었다.
반면 수업이 오픈된 뒤 학생B는 사전 공지창을 뒤늦게 발견하고 이메일 주소를 입력하고 등록하게 되었음. 수업이 이미 오픈되어 있는 상태이기 때문에 기다릴 필요없이 바로 학생B에게 메일로 공지가 가고 수업에 바로 참여할 수 있게 됨(이미 성공적으로 처리된 promise)
이제 코드를 보면서 어떻게 콜백을 쓰지 않고 프로미스 오브젝트를 통해 비동기 코드를 깔끔하게 처리할 수 있는지 알아보자.
프로세스가 무거운 오퍼레이션을 수행중인지, 완료가 되어 성공했는지, 실패했는지 상태에 대해 이해하는 것이 중요
우리가 원하는 데이터를 제공하는 producer과 제공된 데이터를 사용하는 consumers의 차이점 이해
1) 프로미스가 만들어지고 우리가 지정한 오퍼레이션을 수행중일 때에는 pending(작업수행중)
2) 성공적으로 오퍼레이션을 다 끝내게 되면 fulfilled(성공적)
3) 파일을 찾을 수 없거나 네트워크에 문제가 생긴다면 rejected(실패)
const promise = new Promise((resolve, reject) => {
console.log('doing something...');
});
생성자인 executor라는 콜백함수를 전달해주어야 하며 그 안에 또 다른 두 가지 콜백함수를 받음
- resolve : 기능을 정상적으로 수행해서 마지막에 최종 데이터를 전달
- reject : 기능을 수행하다가 중간에 문제가 생기면 호출
promise object complete!
보통 무거운 일을 받는 프로미스는 네트워크에서 데이터를 받아오거나 파일에서 무언가 큰 데이터를 읽어오는 등 시간이 꽤 걸리는 일을 처리하도록 함. 이런 일을 동기적(synchronous)으로 처리해버리면 파일을 읽어오고 네트워크에서 데이터를 받아오는 동안 그 다음 라인에 코드가 실행되지 않아서 시간이 걸리는 일은 비동기적으로 처리하도록 함.
⭐ 네트워크 통신이나 파일을 읽어온다던지 하는 것은 비동기식으로 처리하는 것이 좋음 ⭐
프로미스를 만드는 순간 전달한 executor라는 콜백함수((resolve, reject) => {}) 가 바로 실행됨. 프로미스 안에 네트워크 통신을 하는 코드를 작성했다면 프로미스가 만들어지는 그 순간 네트워크 통신을 수행함
⭐ 만약 네트워크 요청을 사용자가 요구했을 때만 해야 되는 경우라면 즉, 사용자가 버튼을 눌렀을 때 네트워크 요청을 해야 되는 경우라면?
사용자가 요구하지도 않았는데 불필요한 네트워크 통신이 일어날 수 있음. 프로미스는 만드는 순간 함수가 바로 실행되기 때문에 반드시 유의해야함. ⭐
const promise = new Promise((resolve, reject) => {
console.log('doing something...');
setTimeout(() => {
resolve('heechan')
}, 2000);
});
프로미스 안에서 네트워크 통신을 하는 것 처럼 setTimeout으로 시간에 딜레이를 주도록 함.
setTimeout을 이용해 원하는 콜백함수를 2초 정도 있다가 작동할건데 기능을 잘 수행한다면 resolve라는 콜백함수를 호출할 것임.
(네트워크에서 받아온 또는 파일에서 읽어와 가공한 데이터를 resolve라는 콜백함수를 통해 전달)
=> promise가 어떤 일을 2초 정도 하다가 결국 잘 마무리하고 resolve라는 콜백함수를 호출하면서 heechan이라는 값을 전달해주는 promise를 만듦.
promise
.then(value => console.log(value)) // promise return
이렇게 then, catch, finally를 이용해 값을 받아올 수 있음
이전에 promise 변수를 만든 바 있으니 정상적으로 수행이 된다면 then을 호출하고 value 값을 받아오고 우리가 원하는 기능을 수행하는 콜백함수를 전달해주면 됨
value라는 파라미터는 프로미스가 정상적으로 잘 수행되서 마지막으로 resolve 콜백함수에서 전달된 heechan이라는 값이 들어오게 됨.
const promise = new Promise((resolve, reject) => {
console.log('doing something...');
setTimeout(() => {
reject(new Error('no network'))
}, 2000);
});
> Uncaught (in promise) Error: no network
at <anonymous>:5:16
만약 오퍼레이션이 실패했다고 가정하고 2-1-4. 에서 resolve 대신 reject를 호출해봤음. 보통 reject는 Error 클래스로 값을 전달하는데 자바스크립트에서 제공하는 오브젝트 중 하나임. 에러를 처리할 때에는 어떤 에러가 발생했는지 이유를 잘 명시해야만 함.
Uncaught. 잡히지 않은 에러가 발생했다고 하는 것은 2-2. 에서 then으로 성공적인 케이스만 다루었기 때문에 에러 핸들링을 하지 않았기 때문임
promise
.then(value => console.log(value)) // promise return
.catch(error => console.log(error)) // return 된 promise에 catch 등록
> Error: no network
at <anonymous>:5:16
catch를 사용한 결과 더 이상 에러(Uncaught)가 발생하지 않고 catch로 받아온 에러가 로그에 출력되는 것을 확인해볼 수 있음
여기서 헷갈릴 수 있는게 있는데,
promise.then을 호출하게 되면 then은 똑같은 promise를 리턴하기 때문에 리턴된 promise의 catch를 다시 호출할 수 있는 것임
다음은 promise 내부 인터페이스의 모습인데 잘 보면 promise.then했을 때 promise가 리턴되고 리턴된 promise에 catch를 등록해서 사용 가능해지는 것. 이런 것을 chaining이라고 함.
promise
.then(value => console.log(value)) // promise return
.catch(error => console.log(error)) // return 된 promise에 catch 등록
.finally(() => {
console.log('finally')
})
오퍼레이션이 성공하든 실패하든 상관없이 어떤 기능을 마지막으로 수행하고 싶을 때 사용할 수 있음
알아두면 유용한 chaining!
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)); // then은 값을 바로 전달할수도있고 promise를 전달해도 됨. 총 걸리는 시간은 2초
- 서버에서 숫자를 받아오는 새로운 fetchNumber라는 이름의 프로미스 생성
- 서버 통신을 위해 1초 정도 있다가 숫자를 전달해주는 프로미스를 생성(setTimeout)
- then, then, then... 이렇게 다른 비동기적인 애들을 묶여서도 처리할 수 있음(chaining)
const getHen = () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve('🐔'), 1000);
});
const getEgg = hen =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`${hen} => 🥚`), 1000);
setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 1000); // 실패할 경우
});
const cook = egg =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`${egg} => 🍳`), 1000);
});
getHen()
.then(getEgg)
.catch(error => {
return '🍞';
})
.then(cook)
.then(console.log)
.catch(console.log);
> 🐔 => 🥚 => 🍳
> 🍞 => 🍳(실패 시)
여기에 서버로부터 닭을 가져와 계란을 호출하고 계란을 받아와 계란후라이 요리를 호출하는 프로미스를 만들었다.
※ 코드를 더 간결하게 만들기위해 콜백함수를 전달할 때 받아오는 밸류를 다른 함수로 바로 호출하는 경우 생략이 가능
ex) then(hen => getEgg(hen)) --> then(getEgg)
드림코딩 유튜브