Promise / .then

hanbyul.choi·2023년 5월 26일
0

JavaScript 기본

목록 보기
3/3
post-thumbnail

동기 / 비동기

먼저 JavaScript동기적인 프로그래밍 언어이다. 여기서 동기적이란 사용자가 코드를 작성한 순서대로 위에서 아래로 처리한다는 것이다.

그러나 JavaScript에서 비동기적인 처리를 하는 예외 경우가 있다.
fetch같이 데이터를 불러오는 함수는 로딩에 시간이 걸리기 때문에 자바스크립트 자체에서 비동기적인 처리를 하는데, 데이터를 불러오기도 전에 연관된 다른 함수를 실행하면 정상적으로 동작하지 못한다.

따라서 우리는 콜백함수로 fetch나 타이머 같은 비동기적인 처리를 하는 기능들의 동기적 처리
즉, 순서를 보장해줘야하는데, 이 콜백함수를 깔끔하게 만들어 주는 것이 Promise, async 이다.


Promise란,

  • JavaScript에서 비동기 처리를 위해 만들어진 Object이다.

  • 정해진 장시간에 기능을 수행하고 나서 정상적으로 기능이 수행되었을 때 성공의 메세지와 함께 처리된 결과값을 전달해준다.

  • 만약 기능 수행 중 예상치 못한 에러가 발생했다면 그 에러를 전달해준다.

  • 보통 서버에서 데이터를 불러오는 과정에서 장시간이 소요될 수 있기 때문에 이 promise를 활용한다.

    • async/await 기능도 같은 역할이지만 여기서는 Promise만 살펴보자.

Promise의 상태 변화

pending : 사용자가 promise를 만들어서 그 기능을 수행중일 때

fulfilled : 기능을 성공적으로 수행했을 때

rejected : 기능 수행 중 문제가 발생했을 때

[사진 자리]


Promise 사용하기

const promise = new Promise((resolve, reject) => {
	// 비동기 처리할 함수 작성 부분
    console.log("promise"); // promise 선언 시 바로 수행되기 때문에 주의할 것.    
})

resolve : 정상적으로 기능이 수행한 뒤 마지막에 최종데이터를 전달하는 콜백함수

reject : 에러 발생 시 수행되는 콜백함수

resolve

const promise = new Promise((resolve, reject) => {
  console.log('promise'); //즉시 출력
  setTimeout(() => {
    resolve('success');
  }, 2000);
});

// promise가 정상적으로 수행된 이후 실행될 코드
promise.then((value) => {
  console.log(value); // 2초뒤에 promise가 끝나고 넘겨받은 'success' 출력
});

then을 사용하면 promise가 정상적으로 실행된 뒤 resolve의 값을 받아서 우리가 원하는 기능을 수행하는 콜백함수를 실행시킨다.

reject

const promise = new Promise((resolve, reject) => {
  console.log('promise'); // 즉시 출력
  setTimeout(() => {
    // resolve('success');
    reject(new Error('no network'));    
  }, 2000);
});

// promise가 정상적으로 수행된 이후 실행될 코드
promise.then((value) => {
  console.log(value);
})
.catch(error => {
  console.log(error);
});

resolve를 주석처리했기 때문에 promise 이후 reject를 실행하는데 사진처럼 no network 메세지와 함께 에러를 발생시킨다.

발생한 error는 .catch로 핸들링 할 수 있다. 따라서 실제 에러는 발생하지 않고 log에만 출력할 수 있다.


promise 연결하기

// 1초가 걸리는 함수를 세팅하고 resolve로 1을 넘겨준다.
const fetchNumber = new Promise((resolve) => {
  setTimeout(() => resolve(1), 1000);
});

// fetchNumber가 끝나면 실행할 코드
fetchNumber
  .then((num) => num * 2) // 받은 데이터를 사용
  .then((num) => num * 3) // 위 코드가 끝나면 이어서 진행
  .then((num) => {        // then으로 새로운 Promise 사용 가능
    return new Promise((resolve) => {
      setTimeout(() => resolve(num - 1), 1000);
    });
  })
  .then((num) => console.log(num));
  // 두번째 Promise처리가 끝나면 reslove로 넘겨받은 num을 출력
  // 실행 후 2초 뒤 5가 츌력된다. 

이렇게 Promise를 체이닝해서 사용할 수 있다. (reject생략도 가능.)
Promise.then을 호출하면 프로미스를 반환하기 때문에 가능.

Error 핸들링

다음 예시코드는 같이 암탉을 받아와 달걀을 만들어주고 마지막에 계란후라이로 변환하여 콘솔에 출력한다.

// 각각의 promise 코드 작성
const getHen = () => {
return new Promise((resolve, reject) => {
  setTimeout(() => resolve("🐔"), 1000);
});
};

const getEgg = (hen) => {
return new Promise((resolve, reject) => {
  setTimeout(() => resolve(`${hen} => 🥚`), 1000);
});
};
const cook = (egg) =>
new Promise((resolve, reject) => {
  setTimeout(() => resolve(`${egg} => 🍳`), 1000);
});

// then으로 순서를 보장받아 각 promise를 실행한 뒤 마지막에 출력
getHen()
.then((hen) => getEgg(hen))
.then((egg) => cook(egg))
.then((meal) => console.log(meal));

하나의 인자를 넘겨받는 경우 아래처럼 코드생략 가능

getHen() // 이 위치에 주석을 넣으면 자동정렬을 막을 수 있다.
  .then(getEgg)
  .then(cook)
  .then(console.log);

// 주석을 넣지 않으면 정렬 확장 프로그램에 의해 한줄 정렬되어 가독성을 해친다.
getHen().then(getEgg).then(cook).then(console.log);

이제 본격적으로 Error를 발생시켜 핸들링 해보자.

const getHen = () => {
  return new Promise((resolve) => {
    setTimeout(() => resolve("🐔"), 1000);
  });
};

// egg promise에 resolve 대신 reject를 넣어 에러를 발생
const getEgg = (hen) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 1000);
  });
};
const cook = (egg) =>
  new Promise((resolve) => {
    setTimeout(() => resolve(`${egg} => 🍳`), 1000);
  });

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

결과는,

에러가 발생하면서 코드실행이 멈추었다.

이 에러를 핸들링 하려면,

getHen() // 마지막에 catch를 사용하면 발생한 에러를 사용할 수 있다.
  .then(getEgg)
  .then(cook)
  .then(console.log)
  .catch(console.log); // (error => console.log(error))와 같음.

이렇게 에러를 발생시키지 않고 콘솔창에 출력을 하게 된다.

만약 계란을 받아오지 못했을 경우 식빵으로 대체하려고 한다면 아래처럼 작성하면 된다.

getHen() // getEgg 밑에 catch 작성 
  .then(getEgg)
  .catch((error) => "🍞")
  .then(cook)
  .then(console.log)
  .catch(console.log);

출력,

여기서 주의할 점은 , 아래와 같은 상황이다.

new Promise(function(resolve, reject) {
  setTimeout(() => {
    throw new Error("에러 발생!");
  }, 1000);
}).catch(alert);

위 코드는 promise가 처리중일 때 에러가 발생하지 않고 이후에 발생하기 때문에
핸들링 할 수 없다.


// 에러가 없다고 가정하고 리팩토링 했을때

const getMeal = (img) => {
return function (prev) {
return new Promise((resolve) => {
setTimeout(
() => (prev ? resolve(${prev} => ${img}) : resolve(${img})),
1000
);
});
};
};

getMeal("🐔")() //
.then(getMeal("🥚"))
.then(getMeal("🍳"))
.then(console.log)


<hr>

여기까지가 Promise를 간단하게 정리한 내용이다.

0개의 댓글