[JS] Promise란

Leo Bang·2021년 9월 3일
0

fetch() API를 공부하거나, AJAX를 공부하다보면 필연적으로 만나게되는 존재다.

axios나 fetch 둘 다 Promise 객체를 반환하는데, 이전에는 대충대충 then으로 성공할 때 처리하고 catch로 실패할 경우를 처리했었다.

그러던 와중 node.js를 공부하다가 async function을 이용할 일이 생겼는데 async 공식문서를 보고 이해하려면 먼저 Promise가 어떤 놈인지 알아야하더라...

어째 배움의 순서가 뒤죽박죽인 것 같지만, 아무튼 araboza.


1. Description

Promise는 asynchronous operation과 그 operation의 result value을 위한 JavaScript 객체로, asynchronous action의 성공·실패에 따른 value를 handle할 수 있게 해준다.

쉽게 말해서, 비동기 처리를 간단하게 수행할 수 있게 해주는 객체라고 보면 된다.

본 글에서 비동기 처리의 형님인 callback function과의 비교도 다루도록 하겠다 .



2. Promise의 State

  • Pending : 최초의 state. 성공도, 실패도 아님
  • Fulfilled : asynchronous operation이 성공적으로 수행됨
  • Rejected : asynchronous opreation이 실패함

State은 Pending 이후 성공 or 실패만 할 수 있으며 이미 선언된 상태를 변경할 수는 없다



3. Promise의 Constructor

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("메롱")은 선언 즉시 실행되어 콘솔 창에 메시지를 띄운다.



3.1 Resolve와 Reject

executor의 두 parameter인 resolve와 reject는 자바스크립트에서 제공하는 callback method로, 각각 asynchronous operation이 성공했을 때와 실패했을 때의 값을 전달해준다.

이 때 executor 내에서는 반드시 parameter로 넘겨준 resolve나 reject를 1개 이상 호출하여 그 안에 비동기 처리의 결과 값을 전달해주어야 한다.

또한, resolve나 reject로 건네주는 parameter는 1개여야 한다.


Resolve와 Reject의 method명은 고정인가?

다른 이름을 이용해도 괜찮다.

위의 예시에도 그렇게 선언했듯이, (resolve, reject) => {} 대신 (res, rej) => {}을 이용해도 되고, 다른 어떤 함수명을 이용해도 된다.

const myPromise = new Promise((,) => {
  ("성공");
});

myPromise.then((param) => console.log(param))
//  "성공" 

Resolve와 Reject의 method 순서는 고정인가?

Yes.

(resolve, reject) => {}가 아닌 (reject, resolve) => {}을 이용한다면 .then()을 이용할 때 reject를 통해 건네준 값이 전달될 것이고, 반대로 .catch()를 이용할 때 resolve를 통해 건네준 값이 전달될 것이다.


Resolve와 Reject말고 다른 parameter를 넣어도 되는가?

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의 순서만 잘 기억해서 이용하자!



4. Consumer Code: .then(), .catch(), .finally()

executor 함수의 결과 (성공 혹은 실패)를 받아와서 실행할 함수들을 연결할 method들

4.1 then

.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 일 때의 값을 다루는 함수만 전달하는 것이 일반적이다.


4.1.1 Chained Promises

.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"));

// 첫번째 줄
// 두번째 줄
// 세번째 줄
// 네번째 줄

4.2 catch

에러가 발생한 경우만 다루고 싶다면 .catch() 메소드를 이용하면 된다. .catch() 메소드는 reject로 건내주는 값을 처리하는 함수 하나만을 parameter로 받으며, .catch(handleError).then(null, handleError) 와 기능적으로 동일하다.

  • MDN에 따르면 기능적으로 동일한게 아니라 아예 .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");
});		

4.3 finally

Promise가 처리되고 난 후 마지막으로 실행될 함수를 정의하면 된다.

Promise가 성공, 실패 여부와 관계 없이 이행된 후 항상 실행된다는 점에서 .finally(finalFunc).then(finalFunc, finalFunc)와 유사하다.

.finally()를 통해 Promise가 성공했는지 실패했는지는 알 수 없으며, 어차피 보편적으로 절차를 마무리하는 동작을 수행하기 때문에 성공·실패 여부는 몰라도 된다.



5. vs Callback function

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));

짧아진건 모르겠지만 훨씬 가독성이 좋다.




References

profile
me, myself and code

0개의 댓글