[js] promise 개념 이해하기

hyobbang·2022년 9월 16일
0
post-thumbnail

공부하게 된 계기

react를 공부하다가 async와 await를 만났다.
api를 가져와서 state에 json을 담아주는 과정에서 사용한 거였다.
어떻게 사용하는지는 대강 이해가 가는데, 구글링을 하다보니 내가 모르는 promise라는 개념과 많이 엮여있는 거 같아서 한 번 정리를 하고 넘어가려고 한다.

해당 정리는 유투브 드림코딩 채널의 엘리님 강의를 참고하였다.


promise가 대체 무엇인고

promise는 js에서 제공하는 object이다.
비동기를 간단하게 처리할 수 있도록 도와주는 역할을 한다.

간단히 설명하면 이러하다.
정해진 장시간의 기능을 수행하고나서, 성공적으로 수행되었다면 처리된 결과값을 전달한다.
수행에 문제가 발생했다면 에러를 띄운다.

promise는 callback 대신 사용하는 object이다.


promise의 핵심 개념

promise의 핵심은 세 가지다.

  1. state
    요청을 수행 중인 상태 : pending
    요청을 수행 완료한 상태 : fulfilled
    뜻밖의 문제로 요청을 수행하지 못한 상태 : rejected

  2. producer
    원하는 기능을 수행해서 만들어내는 역할 (처음 선언된 promise)

  3. consumer
    원하는 데이터를 소비하는 역할 (callback의 역할을 하는 부분)


producer

promise object 기본형태

const promise = new Promise((resolve, reject) => {
    //시간이 오래 걸리는 작업
});

  • 시간이 오래 걸리는 작업(네트워크 통신, 파일 읽기)을 비동기로 처리하는 이유?

시간이 걸리는 작업은 응답이 언제 올지 알 수가 없다.
때문에 동기로 처리가 되면 다음 코드들이 기약없이 실행되지 않게 된다.
따라서 반드시 비동기로 처리 해줘야한다.



const promise = new Promise((resolve, reject) => {
  //시간이 오래 걸리는 작업
  console.log("doing something...");
});

콘솔에 아무 문자열을 적어서 띄워봤다.

콘솔에 잘 뜬다.
여기서 알 수 있는 사실!

promise를 사용한 순간 코드의 수행이 이루어진다.

만약에 promise로 네트워크 통신을 한다면, promise가 사용되는 순간 네트워크 통신이 이루어지는 것이다.

네트워크 요청을 사용자가 원하는 시점에 해야하는 거였다면, 불필요한 통신이 이루어지게 된 것이다.
기억하자.
새로운 promise가 만들어지면 promise 안에서 작성한 코드는 바로 실행이 된다.



  • producer code 완성
const promise = new Promise((resolve, reject) => {
  //시간이 오래 걸리는 작업
  console.log("doing something...");
  setTimeout(() => {
    resolve("hyobbang");
  }, 2000);
});

2초 뒤에 hyobbang 이라는 결과값을 전달해주는 callback을 실행하는 promise 를 만들어봤다.

consumer

위처럼 값을 받아오는 역할을 하는 promise가 작성이 되었다면,
이제 이것을 이용하는 consumer를 만들 차례다.

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

해당 코드는 resolve가 반환해주는 값을 .then 문법을 써서 value로 받고,
이것을 콘솔에 찍어보는 기능이다.
예상대로 2초 후에 콘솔에 hyobbang이 찍혔다.

error 다루기

그럼 이번엔 reject를 핸들링해보자.

const promise = new Promise((resolve, reject) => {
  //시간이 오래 걸리는 작업
  console.log("doing something...");
  setTimeout(() => {
    //resolve("hyobbang");
    reject(new Error("no network"));
  }, 2000);
});

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

resolve 함수를 주석처리 하고 reject를 만들어준 후에 .catch를 써주면

이렇게 선언한 문자열이 나오며 에러메세지를 띄운다.

finally

에러의 유무와 상관없이 무조건 동작하게 만드는 방법도 있다.

const promise = new Promise((resolve, reject) => {
  //시간이 오래 걸리는 작업
  console.log("doing something...");
  setTimeout(() => {
    resolve("hyobbang");
    reject(new Error("no network"));
  }, 2000);
});

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

finally를 선언하고 아무 인자도 넣지 않은 채로 콘솔에 finally를 찍어보면

이렇게 resolve의 값과 함께 찍히게 된다.
참고로 이건 java에서의 에러 핸들링과 거의 흡사해서 이해하기 쉬웠다.

chaining

const fetchNumber = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000);
});

fetchNumber
  .then((num) => num * 2)
  .then((num) => num * 3)
  .then((num) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(num - 1), 1000);
    });
  })
  .then((num) => console.log(num));

chaining은 이름 그대로 여러 함수를 연결해서 사용하는 것을 뜻한다.
여기서는 .then()을 계속 연결해서 사용해주었다.

fetchNumber라는 promise를 만들어 준 뒤에 2를 곱하고, 3을 곱하고,
새로운 promise에 다시 넣고 -1을 해준 뒤 콘솔에 찍어보는 소스다.

예상대로 2초 후 5가 출력되었다.

여기서 중요한 사실!
.then()은 promise도 전달한다.

콜백지옥을 promise로 탈출하기

callback 포스팅에서 작성했던 코드를 promise를 사용해 깔끔하게 리팩토링 해보도록 하자.

  • 콜백지옥에 갇힌 코드
class UserStorage {
  loginUser(id, password, onSuccess, onError) {
    setTimeout(() => {
      if (
        (id === 'hyobbang' && password === 'great') ||
        (id === 'coder' && password === 'academy')
      ) {
        onSuccess(id);
      } else {
        onError(new Error('not found'));
      }
    }, 2000);
  }

  getRoles(user, onSuccess, onError) {
    setTimeout(() => {
      if (user === 'hyobbang') {
        onSuccess({ name: 'hyobbang', role: 'admin' });
      } else {
        onError(new Error('no access'));
      }
    }, 1000);
  }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');
userStorage.loginUser( //로그인 진행
  id,
  password,
  user => { //loginUser 성공시
    userStorage.getRoles( 
      user,
      userWithRole => { //getRoles성공
        alert( //로그인이 잘 됐다는 메세지 출력
          `Hello ${userWithRole.name}, you have a ${userWithRole.role} role`
        );
      },
      error => { //getRoles실패
        console.log(error);
      }
    );
  },
  error => { //loginUser 실패시 
    console.log(error);
  }
);
  • promise로 리팩토링한 코드
class UserStorage {
  loginUser(id, password) {
    return new Promise((resolve, reject)=>{
      setTimeout(() => {
        if (
          (id === 'ellie' && password === 'dream') ||
          (id === 'coder' && password === 'academy')
        ) {
          resolve(id);
        } else {
          reject(new Error('not found'));
        }
      }, 2000);
    });
  }

  getRoles(user) {
    return new Promise ((resolve, reject) => {
    setTimeout(() => {
      if (user === 'ellie') {
        resolve({ name: 'ellie', role: 'admin' });
      } else {
        reject(new Error('no access'));
      }
    }, 1000);
  });
 }
}

const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');

userStorage 
  .loginUser(id, password)           // (1)로그인 성공하면
  .then(userStorage.getRoles)        // (2)를 수행, 성공하면
  .then(user => alert(               // (3)을 수행 - 로그인이 잘 됐다는 메세지 출력
    `Hello ${user.name}, you have a ${user.role} role`))
  .catch(error => alert('error'));   // error 대응

loginUser와 getRoles가 이중으로 구성되어있던 콜백지옥에서
promise의 사용으로 깔끔하게 분리된 것을 볼 수 있다.
특히나 .then을 사용하여 getRoles가 성공했을 시에 alert를 띄우고, 실패했을 시에 error 메세지를 띄우는 부분이 좀 더 알아보기 쉽게 정리되었다.

profile
매일 따끈따끈한 빵을 굽는 베이커리처럼 코딩하기

0개의 댓글

관련 채용 정보