[JS] Promise(then, catch, finally)

Noah Ko·2022년 6월 6일
1

Javascript

목록 보기
3/6
post-thumbnail

지난번 포스트에서 callback 함수에 대해 알아보고 callback을 많이 사용할 경우 callback 지옥을 경험하게 됨을 확인했다. 그래서 그런 콜백함수를 대체해서 Promise를 어떻게 사용할 수 있는지 적어보려 한다.

1. 그래서 Promise는 도대체 무엇인가.

(왜 하필 Promise라고 이름을 지었는지...작명센스가 최악이다...왜 약속이지...)

일단 객체인데, 비동기를 간편하게 처리할 수 있도록 도와주는 자바스크립트안에 내장되어 있는 객체이다.

2. Promise는 왜 쓰는가.

앞서 말한 것 처럼, Callback 지옥을 경험하고 나서 등장한 것도 있겠지만, Promise는 보통 네트워크 통신이나 파일에서 큰 데이터를 읽는 등의 무거운 작업을 수행할 때 많이 사용된다.

이유는 이러한 작업을 동기적으로 실행한다면 즉, 이 무거운 작업이 다 끝날 때 까지 그 어떤 코드도 실행되지 않고 멈춰서 모두가 이 코드가 끝나기만을 바라보고 있다면 어플리케이션 성능에 아주 치명적이기 때문에, 시간이 걸리는 무거운 작업들은 Promise에 넣어서 처리하여 비동기로 즉, 이거하는 동안 다른 것을 실행 할 수 있도록 하기 위해서 Promise를 사용한다.

3. Promise의 두가지 포인트

  1. Producer와 Consumer 의 차이점, 정보 제공 vs 정보 소비.
  2. state -> process가 무거운 함수를 수행하고 있는 중인지 성공했는지 실패했는지.

3-1. Promise를 만들어보자 : Producer

Producer는 Promise를 생성하는 역할로 자바스크립트에서 제공하는 Promise클래스를 이용하여 만들고, 비동기로 작업할 코드들을 작성하여 그 값을 return해 주는 역할을 한다.

예를 들어서 이렇게 만들어 본다면,

const promise = new Promise((resolve, reejct) => {
  // doing sth heavy work (network or read file)
  // 성공했을 때는 resolve() 
  // 실패했을 때는 reject()
  console.log("doing sth...");
});

console에는 아래내용이 바로 찍히게 된다.

이것이 의미하는 것은 우리가 프로미스를 만드는 순간, 전달한 excutor라는 콜백함수가 바로 실행되는 것을 확인할 수 있다.

그말인 즉슨, Promise안에 네트워크 통신을 하는 코드를 작성한다면, 프로미스가 만들어지는 그 순간 네트워크 통신을 수행하게 되는 것이다.

그래서 만약 사용자가 버튼을 눌렀을 때 네트워크 요청을 해야하는 경우라면, Promise를 생성하는 순간 코드가 바로 실행된다는 것을 유의하여 코드를 작성해야 할 것이다.

이 사실을 간과했다가 불필요한 네트워크 통신을 하는 경우가 왕왕 있다.

When new Promise is created, the excutor runs automatically.

Promise를 생성하는 더 자세한 모습은 아래와 같다.

const promise = new Promise((resolve, reejct) => {
  // doing sth heavy work (network or read file)
  console.log("doing sth...");
  setTimeout(() => {
    resolve("Noah"); // 성공했을 때 Noah라는 값을 return하게 된다.
    reejct(new Error("no network")); // 실패했을 때는 "no network'라는 값을 return하게 된다. 
    //new Error는 자바스크립트에서 제공하는 클래스다.
  }, 2000);
});

3-1. 이번엔 Promise를 사용해보자 : Consumer

Consumer는 return된 Promise를 받은 후에 then, catch, finally로 값을 가공하는 역할을 한다.

위에서 작성한 Producer로 부터 return된 Promise값을 받는 Consumer를 아래와 같이 작성하게 되면,

promise //
  .then((value) => {
    console.log("success", value);
  });

콘솔에는 2초후에 아래와 같이 찍히게 된다.

그런데 만약에 Promise를 return하는 Producer를 아래와 같이 네트워크 통신이 실패한 상황으로 제작하고

const promise = new Promise((resolve, reejct) => {
  // doing sth heavy work (network or read file)
  console.log("doing sth...");
  setTimeout(() => {
    //resolve("Noah"); // 성공했을 때 Noah라는 값을 return하게 된다.
    reject(new Error("no network")); // 실패했을 때는 "no network'라는 값을 return하게 된다. 
    //new Error는 자바스크립트에서 제공하는 클래스다.
  }, 2000);
});

Consumer를 아래와 같이 제작한다면

promise.then((value) => {
  console.log(value);
});

콘솔에는 아래와 같은 Error Message가 찍히게 된다.

이는 우리가 Consumer에서 reject 되었을 때를 처리해 주지 않았기 때문에 발생하는 Error Message이다.

그래서 이러한 경우에는 성공했을 때와 에러가 발생했을 때 둘다를 고려해서 아래와 같이 작성하게 되면,

promise //
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.log(error);
  });

아래와 같이 콘솔에 찍히는 것을 볼 수 있다.

마지막으로 최근에 추가된 finally가 있는데, 이는 성공하든 실패하든 상관없이 실행하고 싶은 기능이 있다면 여기에 넣어주면 된다.

promise //
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    console.log("anyway finally");
  });

이렇게 작성해 주면 성공했을 때든,

실패했을 때든 둘다 출력되는 것을 볼 수 있다.

4. Chaining : 위 값을 받아서

여기서 한가지 짚고 넘어갈 점은 Promise의 then을 호출하게 되면 똑같은 Promise를 return하기 때문에 catch는 그 return 된 Promise에 대해서 catch를 호출 하게 되는 것이다.

즉 chaining이라 함은 연쇄적으로 코드가 실행되는데, 바로 전 값을 받아오는 것을 말한다.

쉽게 말하자면 then, catch는 그위에서 return 된 값에 대한 then과 catch이다.

예를 들어, 아래와 같이 Promise를 return하는 함수가 있을 때

const getHen = () => // 닭을 받아오는 함수
  new Promise((resolve, reject) => {
    setTimeout(() => resolve("🐔"), 1000);
  });

const getEgg = (hen) => // 달걀을 받아오는 함수
  new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error(`error ${hen} => 🥚`)), 1000);
  });

const cook = (egg) => // 받은 달걀로 요리하는 함수
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 1000);
  });

getHen()
  .then((hen) => getEgg(hen)) 
  .catch((error) => { // 위의 return 값에 대한 Error 처리
    return "🥖";
  })
  .then((egg) => cook(egg))
  .then((meal) => console.log(meal))
  .catch(console.log); // 위의 return 값에 대한 Error 처리

두번째 위치한 catch는 getEgg()에서 Promise return이 실패한 경우에 대한 catch이고 오류가 없다면 바로 다음 then으로 넘어가게 되는 것이다. 또한 마지막에 위치한 catch는 그 위 2개의 then에 대한 내용이다.

마무리

암튼, 자바스크립트에서 비동기를 콜백지옥이 아니고 더 간편하게 사용하기 위함을 기억하자.

마지막으로 Callback Hell에서 Promise로 바뀌는지 살펴보고자 한다.

Callback 지옥

// Callback Hell example
class UserStorage { 
  // 로그인 하는 API
  loginUser(id, password, onSuccess, onError) {
    setTimeout(() => {
      if (
        (id === 'ellie' && password === 'dream') ||
        (id === 'coder' && password === 'academy')
      ) {
        onSuccess(id);
      } else {
        onError(new Error('not found'));
      }
    }, 2000);
  }

  getRoles(user, onSuccess, onError) {
    // 역할을 받아오는 API
    setTimeout(() => {
      if (user === 'ellie') {
        onSuccess({ name: 'ellie', role: 'admin' });
      } else {
        onError(new Error('no access'));
      }
    }, 1000);
  }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your passrod');
userStorage.loginUser(
  id,
  password,
  user => { // 로그인 성공했을 때
    userStorage.getRoles( // 성공했을 때 역할을 받아온다.
      user,
      userWithRole => { // 역할가져오기에 성공 했을 때
        alert(
          `Hello ${userWithRole.name}, you have a ${userWithRole.role} role`
        );
      },
      error => { // 역할가져오기에 실패 했을 때
        console.log(error);
      }
    );
  },
  error => { // 로그인 실패했을 때
    console.log(error);
  }
);

아이디와 패스워드를 받아오면 -> 거기서 ID를 받아오고 -> 그 아이디를 잘 받아오게 되면 -> 역할을 가져올 수 있도록 한다.

마지막 부분을 Promise로 바꾸게 되면,

const userStorage = new UserStorage();
const id = prompt("enter your id");
const password = prompt("enter your passrod");
userStorage //
  .loginUser(id, password)
  .then((user) => userStorage.getRoles(user))
  .then((user) => alert(`Hello ${user.name}, you have a ${user.role} role`))
  .catch((error) => console.log(error));

[출처]
https://www.youtube.com/watch?v=JB_yU6Oe2eE (자바스크립트 12. 프로미스 개념부터 활용까지 JavaScript Promise | 프론트엔드 개발자 입문편 (JavaScript ES6))
https://www.youtube.com/watch?v=Sn0ublt7CWM (JavaScript - Promise (then, catch))
https://www.youtube.com/watch?v=PasFh_t1mhY (JavaScript Promise 2 - new Promise)

profile
아코 자네 개발이 하고 싶나

0개의 댓글