[45장] 프로미스

Sheryl Yun·2024년 2월 27일
0
post-thumbnail

등장 이유

  • 자바스크립트는 원래 비동기를 처리하기 위해 콜백 함수를 사용했다.
  • 하지만 전통적인 콜백 패턴은 콜백 지옥으로 인해 가독성이 나쁘고 에러 처리나 여러 개의 비동기를 처리하기에 한계가 있다.
  • ES6에서 이러한 전통적 콜백의 문제점을 해결하기 위해 프로미스를 도입했다.

콜백 지옥 (callback hell)
콜백 함수를 통해 비동기 로직을 처리한 뒤 후속 결과를 가지고 다음 비동기 로직을 또 처리해야 하는 식으로 콜백 함수가 계속 중첩되었을 때 가독성이 떨어지고 코드 복잡도가 증가하는 현상

프로미스 생성

  • Promise 생성자 함수를 new 연산자와 함께 호출하면 프로미스 객체를 생성한다.
  • Promise 생성자 함수는 비동기 로직을 담은 콜백 함수를 인수로 전달받고, 이 콜백 함수는 resolve와 reject라는 함수를 인수로 받는다.
  • 비동기 처리가 성공하면 resolve, 실패하면 reject 함수를 호출한다.
const promise = new Promise((resolve, reject) => {
	if (비동기 처리 성공) {
    	resolve(결과);
    } else { // 비동기 처리 실패
    	reject(실패 이유);
    }
});

프로미스 상태

현재 비동기 처리가 어떻게 진행되고 있는지에 대한 상태 정보이다.

  • pending: 비동기 처리 중
  • fulfilled: 비동기 처리 성공
  • rejected: 비동기 처리 실패

생성 직후의 프로미스는 기본적으로 pending 상태이며 비동기 처리가 완료(settled)된 후 결과가 성공인지 실패인지에 따라 fulfilled 또는 rejected 상태로 변경된다.

const fulfilled = new Promise((resolve) => resolve(1));

// 결과
// Promise {<fulfilled>: 1}
// [[ PromiseStatus ]]: "fulfilled"
// [[ PromiseValue ]]: 1

즉, 프로미스는 비동기 처리 상태비동기 처리 결과를 반환하는 객체이다.

프로미스 후속 처리 메서드

프로미스의 처리 결과를 가지고 후속 처리 메서드를 진행한다.

then

  • resolve, reject 두 개의 콜백 함수를 인수로 전달받는다.
  • then 내부의 비동기 로직이 성공하여 프로미스가 fulfilled 상태가 되면 resolve, 실패하여 rejected 상태가 되면 reject를 호출한다.
  • then 메서드는 언제나 프로미스를 반환하기 때문에 다음 메서드(then 또는 catch)를 계속 실행할 수 있다.

catch

  • 한 개의 콜백 함수만 인수로 전달받는다.
  • 콜백 함수는 프로미스가 rejected 상태일 때만 호출된다.
  • catch 메서드는 언제나 프로미스를 반환하기 때문에 다음 메서드(finally나 또 다른 catch)를 계속 실행할 수 있다.

finally

  • 한 개의 콜백 함수를 인수로 전달받는다.
  • 콜백 함수는 프로미스가 fulfilled이든 rejected이든 상관 없이 무조건 한 번 호출된다.
  • finally 메서드도 언제나 프로미스를 반환한다. (=> Promise의 최종 결과 값: 프로미스)

프로미스 에러 처리

이전의 콜백 패턴(콜백 헬)에서는 에러 처리가 곤란했지만 ES6이 도입한 프로미스는 에러를 효과적으로 처리할 수 있다.

  1. then 메서드의 두 번째 콜백 함수로 에러 처리하기
const wrongUrl = 'https: //jsonplaceholder.typicode.com/XXX/1';

promiseGet(wrongUrl).then(
	res => console.log(res), // 1번째 항: 성공 시 호출
    err => console.log(err) // 2번째 항: 실패 시 호출
);

이 방법은 then의 두 번째 콜백 함수(err => ~)가 첫 번째 콜백 함수(res => ~)에서 발생한 에러를 캐치할 수 없고 코드 가독성도 떨어져서 좋지 않다.

  1. catch 메서드로 에러 처리하기
const wrongUrl = 'https: //jsonplaceholder.typicode.com/XXX/1';

promiseGet(wrongUrl)
	.then(res => console.log(res))
    .catch(err => console.log(err));
);

catch는 내부적으로 then(undefined, onRejected) 형태로 호출되어 다음과 같이 처리된다.

promiseGet(wrongUrl)
	.then(res => console.log(res))
    .then(undefined, err => console.log(err)); // 원래 catch문
);

권장하는 방법

2번의 catch문 사용

catch 메서드를 모든 then 메서드를 호출한 후에 호출하면

  • then 메서드에서 발생한 에러들을 모두 캐치할 수 있고
  • then문을 여러 개 쓰거나 then문 하나에 에러 처리를 모두 하는 것보다 가독성도 더 좋다.

프로미스 체이닝

위에서 살펴봤듯이 then, catch, finally 후속 메서드는 언제나 프로미스를 반환하기 때문에 결과 프로미스를 가지고 다음 후속 처리 메서드를 연이어 실행할 수 있다.

이를 프로미스 체이닝이라고 한다.

프로미스 정적 메서드

Promise.all

  • 여러 개의 비동기 처리를 병렬 처리할 때 사용
  • 처리 결과가 서로 관련이 없는 여러 개의 비동기를 동기적으로 처리할 경우보다 처리 시간이 감소
  • 프로미스로 구성된 배열을 인수로 전달받고 실행한 결과를 배열에 순차적으로 저장
  • 모든 프로미스 요소가 fulfilled되면 처리 결과 배열을 반환하고 하나라도 rejected가 되면 다음 프로미스 처리를 기다리지 않고 바로 종료
  • 인수로 전달받은 요소가 프로미스가 아니라면 Promise.resolve로 자동 래핑

Promise.race

  • 프로미스 배열을 전달받음
  • 모두 처리되기를 기다리지 않고 가장 먼저 fulfilled된 프로미스의 결과 값(resolve 값)을 반환
  • 하나라도 rejected이면 에러를 reject 하는 프로미스 반환

Promise.allSettled

  • 인수로 전달 받은 모든 프로미스의 처리 결과가 담겨 있음
  • fulfilled된 것, rejected된 것 모두 상관 없이 모든 처리 결과를 저장
  • 저장 형태
    • 성공하면 'fulfilled' 상태와 resolve된 값 반환
    • 실패하면 'rejected' 상태와 에러가 발생한 이유 반환
[
	{ status: 'fulfilled', value: 1 },
    { status: 'rejected', reason: Error: Error! at <anonymous>:3:60 }
]
profile
데이터 분석가 준비 중입니다 (티스토리에 기록: https://cherylog.tistory.com/)

0개의 댓글