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));
짧아진건 모르겠지만 훨씬 가독성이 좋다.