[JS] 드림코딩 JS 공부 #12

전예원·2021년 12월 30일
0

Java Script 공부

목록 보기
17/33

💡 promise를 이용해서 콜백 지옥을 무찔러보자!

⭐️ Promise intro


  • JS에서 제공하는, 비동기를 간편하게 처리할 수 있도록 도와주는 object이다.
  • promise는 장시간에 기능을 수행하고 나서 정상적으로 기능이 수행이 되어졌다면 성공의 메세지와 함께 처리된 결과값을 전달해준다.
    만약 기능을 수행하다가 예상치 못한 문제가 발생했다면 에러를 전달해준다.
  • promise에 대해 예를 들어보자면>
    오늘은 12월 31일, 1월 1일에 열리는 강좌의 알림을 받기위해 이메일 주소를 남겨 놓으면, 1월 1일이 되면 promise가 강좌가 오픈되었다는 알림을 이메일로 보내준다. 하지만 1월 2일에 그 공지를 보고 이메일 주소 남긴 사람은 기다릴 필요가 없이 promise가 바로 공지를 보내준다.

🔴 Promise


  • promise 오브젝트는 Async Operation을 위해 쓰인다.
  • 바로 비동기적인 것을 수행할 때 콜백 함수 대신에 유용하게 쓸 수있는 오브젝트
  • promise를 공부할 때 잡고가야할 point!
  1. state 상태
    : 프로세스가 heavy한 operation을 수행하고 있는 중인지? 아니면 이 기능 수행이 다 완료가 되어서 성공했는지? 실패했는지?
    이런 상태에 대해서 이해하는 것이 중요
  2. producer와 consumer의 차이점을 아는 것
    : 우리가 원하는 데이터를 제공하는 사람(producer)과 이 제공된 데이터를 쓰는 사람, 필요로하는 사람(consumer) -> 이 두가지의 다른 견해를 잘 이해하면 된다.

🟤 promise 만화로 설명

  • promise에 관한 것을 두부 사기와 연관지어서 알기 쉽게 적어놓았다.
  • 이 글도 보면 좋을 듯 > 참고

🟠 Promise State


  • 프로미스의 상태는
    프로미스가 만들어져서 우리가 지정한 오퍼레이션이 수행 중 일때는 pending 상태
    오퍼레이션을 성공적으로 끝내게 되면 fufilled state가 된다.
    아니면 파일을 찾을 수 없거나, 네트워크에 문제가 생긴다면 rejected state가 된다.

🟡 Promise 만들기


🟤 Producer

  • 우리가 원하는 기능을 비동기적으로 실행하는 프로미스를 만들어보자!
  • promise는 class라서 new라는 키워드를 이용해서 오브젝트를 생성할 수 있다.
  • promise는 heavy한 일을 많이하는데 그 이유는
    우리가 네트워크에서 데이터를 받아오거나 파일에서 무언가 큰 데이터를 읽어오는 그런 과정들은 시간이 꽤 걸린다.
    이런 것들을 동기적으로 처리하게 되면 우리가 파일을 읽어오고, 네트워크에서 데이터를 받아오는 동안 그 다음 라인에 코드가 실행되지 않기 때문에 시간이 조금 걸리는 일들은 이렇게 promise를 만들어서 비동기적으로 처리하는 것이 좋다.
  • 프로미스를 만드는 순간 우리가 전달한 executor라는 resolve 콜백 함수가 바로 실행되는 것을 확인 할 수 있다.
    만약 우리가 프로미스 안에 네트워크 통신하는 코드를 작성했다면, 프로미스가 만들어지는 그 순간 바로 통신을 수행하게 된다.
  • 만약 네트워크 요청을 사용자가 요구할 때만 해야하는 경우라면, 위와 같은 방식은 불필요한 네트워크 통신이 일어날 수 있다.
  • ✏️ 그래서 promise를 만드는 순간 그 안에 executor라는 콜백함수가 바로 실행이 되니까 이점을 조금 유의하면서 공부해야한다.
// when new Promise is created, the executor runs automatically.
const promise = new Promise((resolve, reject) => {
  // doing some heavy work (network, read files) <- 이런 일들은 비동기적으로 해결하는 것이 좋음
  console.log('doing something...');
  setTimeout(() => {
    resolve('ellie'); // 성공했을 때
    // reject(new Error('no network'));
    // Error라는 클래스는 JS에서 제공하는 object 중에 하나이다.
  }, 2000);
});

🟢 Promise 사용하기


🟤 Consumer (사용하기)

  • then, catch, finally를 이용해서 값을 받아올 수 있다.

🟤 then

  • 위에서 만든 promise변수를 이용해서
    프로미스 값이 정상적으로 잘 수행이 된다면 then을 이용해서 최종적으로 resolve라는 콜백함수를 통해서 전달한 이 값('ellie')이 value의 파라미터로 전달되어서 들어오게 된다.
promise
  .then(value => {
    console.log(value); // ellie
  })

🟤 catch

  • 에러 핸들링을 잘하기 위해선?
  • catch 함수를 이용해 에러가 발생했을 때 어떻게 처리할지 콜백함수를 작성한다.
promise //
  .then(value => {
    console.log(value);
  })
  .catch(error => {
    console.log(error);
  })

🙄 then과 catch 중간 정리

  • promisethen을 호출하게 되면 then은 결국 똑같은 프로미스를 리턴하게 된다.
  • 그 리턴된 promisecatch를 다시 호출 할 수 있는 것이다.
  • 이 것을 Chaining이라고 한다.
  • then을 호출하면 프로미스가 리턴이 되고, 리턴이 된 프로미스에 캐치를 등록하는 것

🟤 finally

promise //
  .then(value => {
    console.log(value);
  })
  .catch(error => {
    console.log(error);
  })
  .finally(() => {
    console.log('finally');
  });
  • 성공, 실패와 상관없이 무조건 마지막에 호출되어지는 아이임
  • 성공 실패 상관없이 어떤 기능을 마지막에 수행하고 싶을 때 사용하면됨

🔵 Promise chaining


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

fetchNumber
  .then(num => num * 2) // 1*2 = 2
  .then(num => num * 3) // 2*3 = 6
  .then(num => {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(num - 1), 1000); // 6 -1 = 5
    });
  })
  .then(num => console.log(num)); // 5
  • then은 값을 바로 전달해도 되고, 또 다른 비동기인 promise를 전달해도 된다.
  • 총 소요되는 시간은 2초가 소요가 된다.
  • then을 여러개 묶어서 다른 비동기적인 아이들도 묶어서 처리할 수도 있다.

🟣 Error Handling


  • 프로미스를 chaining 했을 때, 어떻게 에러를 핸들링 할 수 있는지에 대해서 알아보자!
const getHen = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve('🐓'), 1000);
  });
const getEgg = hen =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${hen} => 🥚`), 1000);
    // setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 1000);
  });
const cook = egg =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 1000);
  });

getHen() //
  .then(hen => getEgg(hen))
  .then(egg => cook(egg))
  .then(meal => console.log(meal));

getHen() //
  .then(getEgg)
  .then(cook)
  .then(console.log);
  • 이런식으로도 작성할 수 있다.
  • 콜백 함수를 전달할 때 받아오는 value를 다른 함수로 바로 하나를 호출하는 경우에는 생략이 가능하다. 그러면 then에서 받아오는 value를 getEgg라는 함수에 암묵적으로 전달해서 호출해준다. 한가지만 받아서 고대로 전달하는 경우에는 위와 같은 방식으로 생략이 가능하다.
  • 프리티어에서 포맷을 저렇게 한줄로 변경하는데 가독성이 매우 좋지 않다. 방지하기 위해서 //를 적어주면된다.

🟤 중간에 에러가 발생한다면?

const getHen = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve('🐓'), 1000);
  });
const getEgg = (hen) =>
  new Promise((resolve, reject) => {
    // setTimeout(() => resolve(`${hen} => 🥚`), 1000);
    
    // 네트워크 문제로 실패하게 된다면?
    setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 1000);
  });
const cook = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 1000);
  });

getHen() //
  .then(getEgg)
  .then(cook)
  .then(console.log);

  • 에러를 핸들링하지 않았다고, 에러가 발생하는 것을 볼 수가 있다.

🟤 catch 사용

getHen() //
  .then(getEgg)
  .then(cook)
  .then(console.log)
  .catch(console.log);

🟤 에러를 잘 핸들링 하고 싶다면?

getHen() //
  .then(getEgg)
  .catch(error => {
    return '🥖'; // 빵꾸처리
  })
  .then(cook)
  .then(console.log)
  .catch(console.log);

  • 전달되어진 에러를 잘 처리해서, 빵으로 대체해서,
    계란을 받아오는 것에 문제가 생겨도 전체적인 promise chain에 문제가 발생하지 않도록,
    빵꾸처리를 해주는 것!!!
  • 계란을 받아오는 것은 실패했지만, 빵으로 대체함으로써 프로미스 체인이 실패하지 않고 결국은 출력을 완료한 것을 볼 수 있다.
  • then -> catch 이런 식으로 바로바로 catch를 사용하면서 에러를 핸들링 할 수 있다.
  • 중간에 에러 처리를 안하면, 결과는 error로 뜨게 된다. 성공을 하지 못한다.

🔥 콜백 지옥을 수정해 보자! 🔥


// Callback Hell example
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 passrod');
userStorage
  .loginUser(id, password)
  .then(userStorage.getRoles)
  .then(user => alert(`Hello ${user.name}, you have a ${user.role} role`))
  .catch(console.log);

🟤 이 사이트 읽어보기 : 참고

profile
앞으로 나아가는 중~

0개의 댓글