
fetch() API를 공부하거나, AJAX를 공부하다보면 필연적으로 만나게되는 존재다.
axios나 fetch 둘 다 Promise 객체를 반환하는데, 이전에는 대충대충 then으로 성공할 때 처리하고 catch로 실패할 경우를 처리했었다.
그러던 와중 node.js를 공부하다가 async function을 이용할 일이 생겼는데 async 공식문서를 보고 이해하려면 먼저 Promise가 어떤 놈인지 알아야하더라...
어째 배움의 순서가 뒤죽박죽인 것 같지만, 아무튼 araboza.
Promise는 asynchronous operation과 그 operation의 result value을 위한 JavaScript 객체로, asynchronous action의 성공·실패에 따른 value를 handle할 수 있게 해준다.
쉽게 말해서, 비동기 처리를 간단하게 수행할 수 있게 해주는 객체라고 보면 된다.
본 글에서 비동기 처리의 형님인 callback function과의 비교도 다루도록 하겠다 .
State은 Pending 이후 성공 or 실패만 할 수 있으며 이미 선언된 상태를 변경할 수는 없다
new Promise()
새 Promise 객체를 생성하며, parameter로 성공할 때와 실패할 때의 method가 담긴 익명의 함수를 담는다.
이 때 전달되는 익명의 함수를 executor (실행자, 실행함수) 라고 한다.
const myPromise = new Promise((resolve, reject) => {
if (1 === 1) {
resolve("success");
} else {
reject("failed");
}
});
이렇게도 표현할 수 있다.
const promiseFunc = (res, rej) => {
if (1 === 1) res("success");
else rej("failed");
};
const myPromise = new Promise(promiseFunc);
참고로 executor 함수는 Promise 객체가 생성될 때 자동으로 실행된다.
예를들어,
const myPromise = new Promise((res, rej) => {
res("test");
console.log("메롱")
});
이렇게 Promise를 선언하면 비동기 처리인 resolve와 reject 함수들은 실행되지 않지만, 동기처리인 console.log("메롱")은 선언 즉시 실행되어 콘솔 창에 메시지를 띄운다.
executor의 두 parameter인 resolve와 reject는 자바스크립트에서 제공하는 callback method로, 각각 asynchronous operation이 성공했을 때와 실패했을 때의 값을 전달해준다.
이 때 executor 내에서는 반드시 parameter로 넘겨준 resolve나 reject를 1개 이상 호출하여 그 안에 비동기 처리의 결과 값을 전달해주어야 한다.
또한, resolve나 reject로 건네주는 parameter는 1개여야 한다.
다른 이름을 이용해도 괜찮다.
위의 예시에도 그렇게 선언했듯이, (resolve, reject) => {} 대신 (res, rej) => {}을 이용해도 되고, 다른 어떤 함수명을 이용해도 된다.
const myPromise = new Promise((뿡, 뽕) => {
뿡("성공");
});
myPromise.then((param) => console.log(param))
// "성공"
Yes.
(resolve, reject) => {}가 아닌 (reject, resolve) => {}을 이용한다면 .then()을 이용할 때 reject를 통해 건네준 값이 전달될 것이고, 반대로 .catch()를 이용할 때 resolve를 통해 건네준 값이 전달될 것이다.
Yes.
근데 함수를 전달하지는 못하고, 값만 전달할 수 있는듯?? (JS에서 함수도 객체 값이긴 한데...)
함수는 해봤는데 안되더라.
const myPromise = new Promise((res, rej, params = "ho") => {
res(params);
})
myPromise.then((params) => console.log(params));
// "ho"
executor 함수에는 resolve과 reject 두 parameter만이 유효하며, 다른 제 3의 parameter를 넣어도 오류가 나지는 않지만 그닥 쓸모가 있지 않은 듯 하다.
Resolve와 Reject의 순서만 잘 기억해서 이용하자!
.then(), .catch(), .finally()executor 함수의 결과 (성공 혹은 실패)를 받아와서 실행할 함수들을 연결할 method들
.then() method의 argument로는
Promise가 성공했을 때의 값을 parameter로 받아와 실행되는 콜백함수
Promise가 실패했을 때의 값을 parameter로 받아와 실행되는 콜백함수
이 둘을 순차적으로 받는다.
const myPromise = new Promise((res, rej) => {
res("aaa");
});
const handleResolve = (param) => console.log(param);
const handleRejected = (param) => console.log(param);
myPromise.then(handleResolve, handleRejected);
2개의 argument를 받아오는게 정석이지만, Promise의 state가 rejected일 때의 값을 다룰 땐 .catch() method를 이용하므로 state가 fulfilled 일 때의 값을 다루는 함수만 전달하는 것이 일반적이다.
.then() 메소드는 항상 새롭게 생성된 Promise객체를 반환한다. 임의로 Promise 객체를 반환하는 callback function을 선언하지 않아도 Promise를 반환하므로, Promise를 연쇄적으로 처리하는데 이용할 수 있다.
이를 chained promises라고 한다.
const myPromise = new Promise((resolve, reject) => {
resolve("첫번째 줄\n");
});
myPromise
.then((params) => params + "두번째 줄\n")
.then((params) => params + "세번째 줄\n")
.then((params) => console.log(params + "네번째 줄\n"));
// 첫번째 줄
// 두번째 줄
// 세번째 줄
// 네번째 줄
혹은 다음과 같이 chained promises를 작성할 수도 있다.
const myPromise = new Promise((resolve, reject) => {
resolve("첫번째 줄\n");
});
const promiseA = myPromise.then((params) => params + "두번째 줄\n");
const promiseB = promiseA.then((params) => params + "세번째 줄\n");
const promiseC = promiseB.then((params) => console,log(params + "네번째 줄\n"));
// 첫번째 줄
// 두번째 줄
// 세번째 줄
// 네번째 줄
에러가 발생한 경우만 다루고 싶다면 .catch() 메소드를 이용하면 된다. .catch() 메소드는 reject로 건내주는 값을 처리하는 함수 하나만을 parameter로 받으며, .catch(handleError)는 .then(null, handleError) 와 기능적으로 동일하다.
.catch(handleError)을 실행할 경우 내부적으로 .then(null, handleError)을 불러온다고 한다. 결과적으로 앞에 null값을 넣은 .then()을 불러오는 것 이므로 역시 새로운 Promise 객체를 return한다.
따라서 catch() 메소드 밑에도 then()이나 catch()를 이용해 chaining을 진행할 수 있다.
const myPromise = new Promise((res, rej) => {
resolve("success");
});
myPromise
.then(value => {
console.log(value); // "success"
throw new Error("ERROR!!");
})
.catch(e => {
console.error(e.message); // "ERROR!!"
})
.then(() => {
console.log("after a catch the chain is restored");
}, () => {
console.log("send Error if failed");
});
이는 다음과 같이도 표현할 수 있다.
myPromise
.then((value) => {
console.log(value); // "success"
return Promise.reject("에러다!");
})
.catch((e) => {
console.error(e); // "에러다!"
})
.then(() => {
console.log("after a catch the chain is restored");
}, () => {
console.log("send Error if failed");
});
Promise가 처리되고 난 후 마지막으로 실행될 함수를 정의하면 된다.
Promise가 성공, 실패 여부와 관계 없이 이행된 후 항상 실행된다는 점에서 .finally(finalFunc)은 .then(finalFunc, finalFunc)와 유사하다.
.finally()를 통해 Promise가 성공했는지 실패했는지는 알 수 없으며, 어차피 보편적으로 절차를 마무리하는 동작을 수행하기 때문에 성공·실패 여부는 몰라도 된다.
const condition1 = false
const condition2 = false
function exampleFunc(callback, errorCallback) {
if (condition) {
callback("성공했다!");
} else if (condition) {
errorCallback({
errorname : "condition 1 실패",
message : "꽝!"
})
} else {
errorCallback({
errorname : "condition 2도 실패",
message : "꽝!!!"
})
}
}
exampleFunc((message) => {
console.log(message);
}, (error) => {
console.log(error.name + " " + error.message);
})
// "condition 2도 실패 꽝!!!"
이걸 Promise로 바꾸면,
const myPromise = new Promise((resolve, reject) => {
if (condition1) {
resolve("성공했다!");
} else if (condition2) {
reject({
errorname : "condition 1 실패",
message : "꽝!"
})
} else {
reject({
errorname : "condition 2도 실패",
message : "꽝!!!"
})
}
});
myPromise
.then((result) => console.log(result))
.catch((error) => console.log(error.errorname + " " + error.message));
짧아진건 모르겠지만 훨씬 가독성이 좋다.