🤔 Promise는 알 수 없는 미래를 설계한다.
Promise는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타낸다.
(...중략...)
Promise는 프로미스가 생성된 시점에는 알려지지 않았을 수도 있는 값을 위한 대리자이다.
-mdn
그러니까, 지금 당장은 수행의 결과를 알지 못하더라도, 호출하는 시점에 뭔가를 수행하고 그것을 수행한 결과값,
혹은 그걸 실패했을 때의 리턴값을 결정해주는 것이다.
여기서 주의할 것이"결과"이다. promise 작성할 때엔 어떤 결과가 나올지 알 수가 없다. 하지만 우리는 그 결과값을 때에따라 전달해야하고, 다음 행동까지 정해줘야한다.
때문에,"어느 시점에 어느 환경에서 이를 호출했을 때에, 그 당시의 결과 값을 전달하거나 말거나 해서 그 다음 뭔가를 할것이다"를 컴퓨터에 알려주는 것이다.
😮 Promise는 왜 쓰이는가
OK, 그니까 미래에 실행해서 성공이나 실패했을때의 결과값 얻고 그 다음을 약속해준다는 거지?
근데 왜 꼭 그렇게 작성해야하지?
에어비앤비 사이트 숙소 정보 페이지를 한 번 뜯어보자
우리가 에어비앤비 사이트에 가면 서버에 다양한 정보를 요청하게 된다.
그리고 각각의 요청은 생각보다 많은 후작업이 필요하다.
예를 들어
"사진 링크 서버에서 긁어와줘"
"사진 링크 있어? 그럼 그 링크를 html의 img태그 src에 넣어줘"
"사진 링크 없어? 그럼 등록된 사진이 없다고 띄워줘"
등등 각각의 경우에 따라 많은 후속 작업들이 필요하며 이 후속 작업들은 경우에 따라 다른 경로로 흐르게 된다.
이렇게 각각의 경우들이 다음의 행동들로 이어져 나가는 것을 promise chain이라고 부른다.
이렇게 언제 올지 모르는 비동기의 결과값으로 다음 작업을 미리 설계해 놓는 작업이 promise로 이루어진다.
즉, promise는 비동기 작업을 동기적으로 제어하기위해 탄생했다.
이런 promise는 callback으로도 대체가 될 수 있으나, 너무나 코드가 복잡하고 가독성이 떨어져
callback hell에 빠질 위험이 있기 때문에, 좀더 스맛뜨 하게 문제를 해결하기에는 promise가 좋으며,
웹하고의 통신에서 fetch api같은 것도 promise로 이루어져있기 때문에,
우리는 promise를 잘 알아야 하는 것이다.
👥 Promise의 형식은 객체이다.
const pr = new Promise((resolve,reject)=>{})
typeof pr //object
자바스크립트의 많은 것들이 객체로 이루어져있듯이,
Promise도 하나의 객체이다. 다만 특별한 구조로 짜여진 객체!
그래서 우리는 이 특별한 객체의 형식을 갖추고 있는 인스턴스들을 만들어 사용한다.
🤝 Promise인스턴스를 만들땐 executor를 인자로 전달해줘야 한다.
promise를 처음 만들때, 이렇게 해주면 된다.
const createPromise = ()=>{
return new Promise(functioin(resolve,reject){
//executor
})
}
이때 아래의 부분을 executor(실행자, 실행함수)라고 한다.
📌promise는 만들어질 때 자동으로 executor실행시킨다.
📌그렇기 때문에 함수안에 promise 자체를 리턴하도록 짜주는 경우가 많다.
그러면 함수가 실행되면 > promise리턴&executor실행 > result 전달 혹은 error로 마무리!
이렇게 함수가 실행되는 순간 모든 설계된 promise chain을 수행하게 되는 것이다.
executor 안에는 아래아래에서 설명할 resolve와 reject가 들어갈 수 있다.
🔑 Promise 인스턴스 안에는 state와 result라는 property가 있다.
onst pr = new Promise((resolve,reject)=>{})
console.dir(pr)
위 의 결과는 아래와 같다.
보시다시피, [[PromiseState]]와 [[PromiseResult]라는 키가 정해져있다.
이중에 PromiseState 프로퍼티는 promise의 상태를 나타낸다.
그 상태는 아래의 세가지가 있다.
promise 인스턴스의 state세가지 | |
대기(pending) | 이행하지도, 거부하지도 않은 초기 상태 |
이행(fulfilled) | 연산이 성공적으로 완료됨 |
거부(rejected) | 연산이 실패함 |
그리고 PromiseResult 프로퍼티는 promise가 연산이 되었을 때에 전해지는 값으로,
resolve()와 reject() 괄호안에 return 값으로 작성된 값이 들어간다.
(reject시 -> reject('결과값')의 결과값이 PromiseResult로,
resolve시 -> resolve('결과값')의 결과값이 PromiseResult로!)
코드)
const gaebokchiPromise = (str)=>{
return new Promise((resolve,reject)=>{
if(str === 'lucky'){
resolve('먹이를 발견했다!🐟')
}
else if(str === 'unlucky'){
reject('물이 차가워 돌연사!☠️')
}
})
}
console.dir(gaebokchiPromise('unlucky'))
결과)
📌 Promise는 resolve와 reject라는 메서드가 있다.
promise의 메서드가 여러가지가 있는데, 그 중 가장 많이 쓰이는 것이 바로
resolve와 reject이다.
코드)
const pr = new Promise((resolve,result)=>{})
console.dir(pr.__proto__.constructor)
결과)
내가 만든 Promise의 인스턴스를 __proto__와 constructor로 근원을 따라가다보면 이렇게
reject와 resolve 메서드가 정의되어 있는 것을 볼 수 있다.
이 resolve와 reject는 promise객체 안에서 각각 성공시 전달될result값과 실패시 전달될 실패이유를 정해준다.
좀 많이 오래된 게임이긴 하지만 (이것보다 적절한 예를 못찾았기 때문에...)
추억의 개복치 키우기 게임을 생각해보자
오른쪽 사진의 상황에서
개발자는 <진행!>버튼을 누를 때 유저가 받을 50:50을 다음과 같이 설계한다.
promise) 개복치가 깊은 곳까지 헤엄치도록 한다.
resolve) 성공시! 먹이를 얻고 성장!
reject) 실패시! 물이 차가워 돌연사!
promise안에 이렇게 resolve와 reject로 성공시와 실패시의 결과값을 연결해주는 것이다.
resolve로 성공이 되면 그 다음 promise로 연결이 되고
개복치는 또 다른 선택을 이어나가며 생사의 줄다리기(promise)를 계속할테지만,
reject로 실패가 되면 돌연사! 가 뜨고 게임을 다시 시작할건지 등의 경우가 이어질 것이다.
resolve와 reject는 아래와 같이 쓰인다.
const gaebokchiPromise = (str)=>{
return new Promise((resolve,reject)=>{
if(str === 'lucky'){
resolve('먹이를 획득했다!🐟')
}
else if(str === 'unlucky'){
reject('물이 차가워 돌연사!☠️')
}
})
.then((result)=>{
console.log(result)
})
.catch((error)=>{
console.log(error)
})
}
gaebokchiPromise('lucky') //'먹이를 획득했다!🐟'
gaebokchiPromise('unlucky') //'물이 차가워 돌연사!☠️'
이 resolve와 reject의 결과값은 then과 catch를 사용해서 다음 promise객체로 넘어간다.
then과 catch에 대해서 아래에서 이어서 설명한다.
📌 Promise를 연결하고 result를 받는 친구들 then과 catch
promise인스턴스들에서 사용할 수 있는 메서드에는 then,catch,finally가 있다.
코드)
const pr = new Promise((resolve,result)=>{})
console.dir(pr.__proto__)
결과)
먼저 then부터 살펴보자.
then(f,f)은...
- promise인스턴스에 사용한다.
- promise를 반환한다.
- 함수 두개를 인자로 받는다.
- resolve()시 결과값(onFulfilled)을 첫번째 인자 함수에 받아 실행한다.
- reject()시 결과값(onRejected)을 두번째 인자 함수에 받아 실행한다.
function hunt(food){
return `${food} 사냥에 성공했다.`
}
const gaebokchiPromise = (str)=>{
return new Promise((resolve,reject)=>{
if(str === 'lucky'){
resolve('먹이를 발견했다!🐟')
}
else if(str === 'unlucky'){
reject('물이 차가워 돌연사!☠️')
}
})
.catch((result)=>{
console.log(result) //'물이 차가워 돌연사!☠️'
})
.then((result)=>{
console.log(result) //undefined
},
(error)=>{
console.log(error)
})
.then((result)=>{
console.log(result) //undefined
},
(error)=>{
console.log(error)
})
}
gaebokchiPromise('unlucky')
//'물이 차가워 돌연사!☠️'
//undefined
//undefined
catch(f)는...
- promise인스턴스에 사용한다.
- promise를 반환한다.
- 인자 함수는 하나 밖에 없다.
- reject()시 결과값(onRejected)을 받아 실행한다.(resolve시 함수에 들어오지 않음)
catch는 then의 두번째 인자로 들어오는 onRejected, 즉 실패시에 실행되는 함수만 똑!
떼어서 받을 수 있는 메서드이다.
실제로는 이렇게 프로미스를 then(f,f)에 2단 핸들러보다는 catch를 사용해 처리하는 경우가 많다고 한다.
그도 그럴것이, 성공시는 then, 실패시는 catch로 걸러주면, 눈에 딱! 들어오기 때문에 읽기 좋다.
catch와 then으로 여러가지 실험을 해본 결과가 궁금하다면 아래의 '더보기' 클릭
--------
then과 catch, 그리고 전달값에 대한 고찰
then과 catch는 모두 promise를 반환한다.
그러므로 then이던 catch든 promise로 연결된 체인은 끊어지지 않는다.
다만 result는 다른 문제이다.
처음 promise를 만들면서 resolve와 reject를 작성하면
그 다음 연결되는 then에 자동으로 각각의 경우에 맞게 성공하면 resolve의 결과값이,
실패하면 reject의 결과가 전달된다.
하지만 그 뒤에 또 then으로 인자를 받기 위해선 return 을 시켜줘야한다.
- catch(f)이후에 또 에러가 있었던게 아니라면 중복된 catch(f)는 통과한다.
- resolve()되고 다른 에러도 없다면 catch(f)는 그냥 통과한다.
- resolve()와 reject()는 각경우에 맞는 값을 자동으로 return을 시키는 것이다.
- then(f)은 다음 then(f)에게 return을 시키지 않으면 별도의 값을 전달하지 않는다.
- catch(f)도 다음 then(f)에게 return을 시키지 않으면 별도의 값을 전달하지 않는다.
- catch(f)가 return한 값은 다음 then(f)에게 첫번째 인자로서 전달된다.(then(f,f) 두번째인자말고)
resolve시
const gaebokchiPromise = (str)=>{
return new Promise((resolve,reject)=>{
if(str === 'lucky'){
resolve('먹이를 획득했다!🐟')
}
else if(str === 'unlucky'){
reject('물이 차가워 돌연사!☠️')
}
})
.then((result)=>{
console.log('첫번째 then의 첫번째인자') //'첫번째 then의 첫번째인자'
console.log("result: " + result) //'result: 먹이를 획득했다!🐟'
},
(error)=>{
console.log('첫번째 then의 두번째인자')
console.log("error: " + error)
return error;
}
)
.then((result)=>{
console.log('두번째 then의 첫번쨰인자') //'두번째 then의 첫번쨰인자'
console.log("result: " + result) //'result: undefined'
return "리턴을 시켜주면 result가 생기지요"
},
(error)=>{
console.log('두번째 then의 두번째인자')
console.log("error: " + error)
return error;
}
)
.then((result)=>{
console.log('세번째 then의 첫번째인자') //'세번째 then의 첫번째인자'
console.log("result: " + result) //'result: 리턴을 시켜주면 result가 생기지요'
},
(error)=>{
console.log('세번째 then의 두번째자')
console.log("error: " + error)
}
)
.then((result)=>{
console.log('네번째 then의 첫번째인자') //'네번째 then의 첫번째인자'
console.log("result: " + result) //'result: undefined'
},
(error)=>{
console.log('네번째 then의 두번째인자')
console.log("error: " + error)
}
)
.then((result)=>{
console.log('다섯번째 then의 첫번째인자') //'다섯번째 then의 첫번째인자'
console.log("result: " + result) //'result: undefined'
},
(error)=>{
console.log('다섯번째 then의 두번째인자')
console.log("error: " + error)
}
)
}
gaebokchiPromise('lucky')
reject시 - then 사용
const gaebokchiPromise = (str)=>{
return new Promise((resolve,reject)=>{
if(str === 'lucky'){
resolve('먹이를 획득했다!🐟')
}
else if(str === 'unlucky'){
reject('물이 차가워 돌연사!☠️')
}
})
.then((result)=>{
console.log('첫번째 then의 첫번째인자') //'첫번째 then의 두번째인자'
console.log("result: " + result) //'error: 물이 차가워 돌연사!☠️'
},
(error)=>{
console.log('첫번째 then의 두번째인자')
console.log("error: " + error)
return error;
}
)
.then((result)=>{
console.log('두번째 then의 첫번째인자') //'두번째 then의 첫번쨰인자'
console.log("result: " + result) //'result: 물이 차가워 돌연사!☠️'
return "리턴을 시켜주면 result가 생기지요"
},
(error)=>{
console.log('두번째 then의 두번째인자')
console.log("error: " + error)
return error;
}
)
.then((result)=>{
console.log('세번째 then의 첫번째인자') //'세번째 then의 첫번째인자'
console.log("result: " + result) //'result: 리턴을 시켜주면 result가 생기지요'
},
(error)=>{
console.log('세번째 then의 두번째자')
console.log("error: " + error)
}
)
.then((result)=>{
console.log('네번째 then의 첫번째인자') //'네번째 then의 첫번째인자'
console.log("result: " + result) //'result: undefined'
},
(error)=>{
console.log('네번째 then의 두번째인자')
console.log("error: " + error)
}
)
.then((result)=>{
console.log('다섯번째 then의 첫번째인자') //'다섯번째 then의 첫번째인자'
console.log("result: " + result) //'result: undefined'
},
(error)=>{
console.log('다섯번째 then의 두번째인자')
console.log("error: " + error)
}
)
}
gaebokchiPromise('unlucky') //'물이 차가워 돌연사!☠️'
//'첫번째 then의 두번째인자'
//'error: 물이 차가워 돌연사!☠️'
//'두번째 then의 첫번째인자'
//'result: 물이 차가워 돌연사!☠️'
//'세번째 then의 첫번째인자'
//'result: 리턴을 시켜주면 result가 생기지요'
//'네번째 then의 첫번째인자'
//'result: undefined'
//'다섯번째 then의 첫번째인자'
//'result: undefined'
reject시 - catch 사용
const gaebokchiPromise = (str)=>{
return new Promise((resolve,reject)=>{
if(str === 'lucky'){
resolve('먹이를 획득했다!🐟')
}
else if(str === 'unlucky'){
reject('물이 차가워 돌연사!☠️')
}
})
.catch((error)=>{
console.log(error) //'물이 차가워 돌연사!☠️'
return error
}
)
.then((result)=>{
console.log('첫번째 then의 첫번째인자') //'첫번째 then의 첫번째인자'
console.log("result: " + result) //'result: 물이 차가워 돌연사!☠️'
},
(error)=>{
console.log('첫번째 then의 두번째인자')
console.log("error: " + error)
return error;
}
)
.catch((error)=>{
console.log(error)
return error
}
)
.then((result)=>{
console.log('두번째 then의 첫번째인자') //'두번째 then의 첫번쨰인자'
console.log("result: " + result) //'result: undefined'
return "리턴을 시켜주면 result가 생기지요"
},
(error)=>{
console.log('두번째 then의 두번째인자')
console.log("error: " + error)
return error;
}
)
.then((result)=>{
console.log('세번째 then의 첫번째인자') //'세번째 then의 첫번째인자'
console.log("result: " + result) //'result: 리턴을 시켜주면 result가 생기지요'
},
(error)=>{
console.log('세번째 then의 두번째자')
console.log("error: " + error)
}
)
.then((result)=>{
console.log('네번째 then의 첫번째인자') //'네번째 then의 첫번째인자'
console.log("result: " + result) //'result: undefined'
},
(error)=>{
console.log('네번째 then의 두번째인자')
console.log("error: " + error)
}
)
.then((result)=>{
console.log('다섯번째 then의 첫번째인자') //'다섯번째 then의 첫번째인자'
console.log("result: " + result) //'result: undefined'
},
(error)=>{
console.log('다섯번째 then의 두번째인자')
console.log("error: " + error)
}
)
}
gaebokchiPromise('unlucky') //'물이 차가워 돌연사!☠️'
//'물이 차가워 돌연사!☠️'
//'첫번째 then의 첫번째인자'
//'result: 물이 차가워 돌연사!☠️'
//'두번째 then의 첫번쨰인자'
//'result: undefined'
//'세번째 then의 첫번째인자'
//'result: 리턴을 시켜주면 result가 생기지요'
//'네번째 then의 첫번째인자'
//'result: undefined'
//'다섯번째 then의 첫번째인자'
//'result: undefined'
--------
finally(f)는...
- then, catch와 달리 어떠한 인자도 받지 않는다. (Pormise chaining으로 받아오는 result값이 없다.)
- 따라서 resolve() reject()여부에 따라 상관없이 뭔가를 수행할 필요가 있을 때 쓴다.
- 이전 promise로부터 result값을 받아오거나 result로 뭔가를 할 필요가 없을 때 쓴다.
- 중간에 써도 상관은 없으나, resolve()나 reject()결과값은 finally를 건너뛰고 전달된다.
resolve시
const gaebokchiPromise = (str)=>{
return new Promise((resolve,reject)=>{
if(str === 'lucky'){
resolve('먹이를 획득했다!🐟')
}
else if(str === 'unlucky'){
reject('물이 차가워 돌연사!☠️')
}
})
.finally(()=>{
console.log('finally') //'finally'
})
}
gaebokchiPromise('lucky')
//'finally'
reject시
const gaebokchiPromise = (str)=>{
return new Promise((resolve,reject)=>{
if(str === 'lucky'){
resolve('먹이를 획득했다!🐟')
}
else if(str === 'unlucky'){
reject('물이 차가워 돌연사!☠️') //Uncaught (in promise) 물이 차가워 돌연사!☠️
}
})
.finally(()=>{
console.log('finally') //'finally'
})
}
gaebokchiPromise('unlucky')
//Uncaught (in promise) 물이 차가워 돌연사!☠️
//'finally'
🤘🏻 Promise를 비동기적으로 실행시키도록 해주는 메서드 all
- 인자로 promise로 이루어진 배열을 넣는다.
- 리턴 값도 result로 이루어진 배열로 받는다.
- 주어진 promise중 하나라도 reject()되면 그 결과값을 반환하며 다른 promise야 어찌되건 reject시킴
all 역시 resolve와 reject 와 마찬가지로 원형의 promise에 사용가능한 메서드이다.
promise와 then이 동기적으로 수행했다면,
promise all은 한꺼번에 promise를 실행하고 한꺼번에 답을 내주어 비동기적으로 사용하도록 해준다.
resolve시
const promise1 = (str)=>{
return new Promise((resolve,reject)=>{
if(str === 'lucky'){
resolve('먹이를 획득했다!🐟')
}
else if(str === 'unlucky'){
reject('물이 차가워 돌연사!☠️')
}
})
}
const promise2 = Promise.resolve(3);
const promise3 = 42;
const promise4 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1,promise1('lucky'), promise2, promise3,promise4]).then((values) => {
console.log(values);
});
//[ ƒ promise1(), '먹이를 획득했다!🐟', 3, 42, 'foo' ]
reject시
const promise1 = (str)=>{
return new Promise((resolve,reject)=>{
if(str === 'lucky'){
resolve('먹이를 획득했다!🐟')
}
else if(str === 'unlucky'){
reject('물이 차가워 돌연사!☠️')
}
})
}
const promise2 = Promise.resolve(3);
const promise3 = 42;
const promise4 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1,promise1('unlucky'), promise2, promise3,promise4])
.then((values) => {
console.log(values);
});
//Uncaught (in promise) 물이 차가워 돌연사!☠️
👀 2022.8.7추가글
react 문서에서 fetch로 전달되는 promise의 에러핸들링을 catch보다는 then을 권하고 있어 긁어와봤다.
🫣관련글
'개념 > javaScript' 카테고리의 다른 글
[javaScript] fs모듈, fs.readFile (0) | 2022.08.01 |
---|---|
[javaScript] async/await (0) | 2022.08.01 |
[javaScript] 비동기, Promise,async/await (0) | 2022.07.26 |
[javaScript] 프로토타입 체인 (0) | 2022.07.25 |
[javaScript] 정규 표현식 (0) | 2022.07.24 |