Promise

프로미스를 쉽게 알아보자

상점에서 물건을 주문하는데, 그 물건이 오기까지 시간이 걸린다고 가정을 해보자
또한 언제 물건이 오는지는 알 수 없을 뿐더러 가끔 물건을 만들다가 실패하는 경우도 있다고 한다.

이럴땐 소비자는 어떻게 해야할까?
10초에 한번씩 진행 상황을 물어보고 답을 받는다고 한다면,
물어봤을 때, 준비의 여부와 또는 실패했다는 답이 온다면, 다시 물건을 주문을 하면 된다.

하지만 그것 보다는 이럴땐 상점에 주문을 하고 상품이 준비되었거나 실패하면 알려달라고 약속을 하면서, 전화번호를 주고 나오는것이 나을 것이다.
상품이 준비되는 동안 다른 작업도 할 수 있고, 상점을 이번호를 기억했다고 작업이 완료되거나 실패했을 떄, 소비자에게 알려주면 되니깐 말이다.
이럴때 사용할 수 있는게 바로 프로미스(Promise)이다.


프로미스 사용법

프로미스는 아래와 같이 사용한다.

const pr = new Promise((resolve, reject) => {
	// 코드~
});

new Promise로 생성하고, 함수를 전달받고, 인수는 resolve와 reject로 두개를 받는다.

resolve: 성공한 경우 실행되는 함수
reject: 실패한 경우 실행되는 함수
🙋🏻‍♂️ 어떤 일이 완료된 이후에 실행되는 함수를 콜백(callback)함수라고 한다.


new Promise 생성자가 반환하는 프로미스 생성객체는 state와 result를 프로퍼티를 받는다. state는 초기에 pending(대기)고 result는 undefined였다가 resolve가 호출되면(성공하면), fulfilled(이행됨)가 된다. 이때 reult는 resolve함수로 전달 된 값이 된다.

하지만 reject가 호출되면(실패하면), rejected가 된다. 이때 result는 reject함수로 전달 된 에러이다.


코드로 살펴보기

예제 1 // then, catch, finally

위에서 알게 된 내용을 코드를 통해서 살펴보자..
3초가 걸리도록 setTimeout을 이용했다. state와 result는 3초 뒤 성공과 실패의 여부에 따라서 다음 주석과 같이 변하게 될 것이다.

성공을 가정한 코드

	const pr = new Poromise((resolve, reject) => {
    	setTimeout(() => {
        	resolve('OK')
        }, 3000)
    });

// state: pending(대기) --->(3초 뒤..) fulfilled(이행 됨)
// result: undefined --->(3초 뒤..)	'OK'

실패를 가정한 코드

	const pr = new Poromise((resolve, reject) => {
    	setTimeout(() => {
        	reject(new Error('Error!'))
        }, 3000)
    });

// state: pending(대기) --->(3초 뒤..) rejected(거부 됨)
// result: undefined --->(3초 뒤..)	'Error!'

then (소비자 입장에서의 코드..?)

지금까지의 코드가 소비자에게 성공 실패 여부를 알리는 판매자의 코드였다면, 이번엔 소비자입장에서의 코드를 알아보자!

	const pr = new Poromise((resolve, reject) => {
    	setTimeout(() => {
        	resolve('OK')
        }, 3000)
    });

	pr.then(
    	function (result){
        // 성공했을 떄, 실행되는 코드
          console.log(result);
        }, 
     	function (err){
        // 실패했을 떄, 실행되는 코드
          console.log(err);
        } 
    );

성공했을 때, function(result)의 result에는 위의 reolve('OK')의 'OK'값이 들어오게 된다.
실패했을 때, function(err)의 err에는 에러값이 들어오게 된다.

  • 🤔 위의 코드는 resolve로 실행되었기 때문에, err의 경우는 실행되지 않겠지만 말이다.

then이외의 사용할 수 있는 녀석들 (catch, finally)

then이외에 사용할 수 있는것들이 있는데, 바로 catch와 finally이다.

  • catch: 에러가 발생한 경우, 즉 reject가 발생한 경우에만 실행된다는 특징이 있다.
    👇 이렇게 바꿀 수 있다.
pr.then(
    	function (result){
        // 성공했을 떄, 실행되는 코드
          console.log(result);
        }, 
    ).catch(
  		// 실패했을 떄, 실행되는 코드
		  function (err){
          console.log(err);
        } 
	)

명확하게 구분해 줌으로 코드의 가독성으로도 좋고, 첫번째 함수가 실행되었다가 나오는 에러도 잡아 줄 수 있는 장점이있다. (걍 이거 꼭 써라)

  • finally: 성공이나 실패의 여부 상관 없이 무조건 실행이 되는 함수이다.
	pr.then(
    	function(result){}
    ).catch(
    	function(err){}
    ).finally(
    	function(){
        	console.log('끝~!')
        }
    )

로딩화면 같은 것들을 없앨 때, 매우 유용하다.

예제 2 // 콜백지옥

callback을 사용해보자.. (프로미스 사용 X +콜백 지옥이란?)

const order1 = (callback) => {
	setTimeout(function(){
    	console.log("1번 주문 끝남여!")
    })
};

const order2 = (callback) => {
	setTimeout(function(){
    	console.log("1번 주문 끝남여!")
    })
};

const order3 = (callback) => {
	setTimeout(function(){
    	console.log("1번 주문 끝남여!")
    })
};

console.log('주문 받아요')
order1(function(){
	order2(function(){
    	order3(function){
        	console.log('주문 안받아요 이제!')
        }
    })
})

프로미스를 사용하지 않고, 콜백을 호출해서 사용해보았다.

// 주문 받아요
// 1번 주문 끝남여!
// 2번 주문 끝남여!
// 3번 주문 끝남여!
// 주문 안받아요 이제!

잘 동작하는 모습을 볼 수 있다.
점점 뎁스(depth)가 길어지면서 콜백을 호출하는 것을 callback hell(콜백 지옥)이라고 부른다.
이번에는 콜백 호출 부분을 프로미스를 사용해여 표현해보자

callback 호출 부분을 promise로 바꿔보자..

const order1 = () => {
	return new Promise((res, rej) => {
    	setTimeout(() => {
        	res("1번 주문 끝남여!")
        }, 1000);
    })
};

const order2 = (message) => {
  	console.log(message);
	return new Promise((res, rej) => {
    	setTimeout(() => {
        	res("2번 주문 끝남여!")
        }, 3000);
    })
};

const order3 = (message) => {
  	console.log(message);
	return new Promise((res, rej) => {
    	setTimeout(() => {
        	res("3번 주문 끝남여!")
        }, 2000);
    })
};

console.log('주문 받아요')
order1()
.then((res) => order2(res))
.then((res) => order3(res))
.then((res) => console.log(res))
// 에러가 났을 때를 대비
.catch(console.log)
// 성공 실패 여부 상관없이 무조건 실행
.finally(() => {
	console.log("주문 안받아요 이제!"));
})
  • 처음 order1을 실행하고, order1에서 프로미스를 반환하고 resolve함수로 실행하면서 넘겨준 값을 oder2에 넘겨주면 된다.
  • order2는 메세지를 받아서 콘솔로그를 찍어주고, 마찬가지로 프로미스를 반환한다. 이때, resolve함수로 전달한 '2번 주문 끝남여!'를 order3에서도 또 사용할 수가 있게된다.
  • order3 역시 order2와 같다. 여기서 order3가 마지막으로 넘겨받은 메세지를 콘솔로 찍어서 마무리했다.
// 주문 받아요
// 1번 주문 끝남여!
// 2번 주문 끝남여!
// 3번 주문 끝남여!
// 주문 안받아요 이제!

이렇게 프로미스가 연결이 반복되는것을 Proimse chaning(프로미스 체이닝)이라고 한다.
만약에 2번의 주문을 rej로 실패를 일으킨다고 가정한다면 다음과 같은 결과가 나올 것이다.

const order2 = () => {
	return new Promise((res, rej) => {
    	setTimeout(() => {
        	rej("에러~")
        }, 3000);
    })
};

// 주문 받아요
// 1번 주문 끝남여!
// 에러~
// 주문 안받아요 이제!

예제3 // Promise.all

예제 2에서는 모든 주문을 받는데 걸리는 시간이 6초였다.
만약에 3개의 주문을 동시에 받는다고 가정한다면, 가장 오래 걸리는 2번 주문(3초)을 받았을 때에는 모든 주문을 받은 상태일 것이다.
이럴 때 사용할 수 있는것이 바로 Promise.all이다.

Promise.all 사용해보자..

주문은 예제 2와 동일하다고 가정하고 윗 부분을 생략하고 Promise.all을 구현해보려고 한다.

Promise.all([order2(), order2(), order3()]).then((res) => {
	console.log(res);
});

Promise.all을 하고 배열로 받은다음에 then을 사용해서 res값을 콘솔로 찍어주었다.
배열안의 이 3개의 모든 작업이 완료가 되어야만 then이 실행될 것이다.

["1번 주문 끝남여!", "2번 주문 끝남여!", "3번 주문 끝남여!"]

각 프로미스가 넘겨준 값이 배열로 들어온 모습을 볼 수 있다.

🙋🏻‍♂️ 정리하자면 Promise.all은 한꺼번에 시작하고 모두 이행되면 값을 사용할 수 있다.

🤔 만약에 reject가 일어나면 어떻게 될까?

Promise.all에서 reject(실패)가 일어난다면..

2번 주문에서 reject가 일어난다고 가정하고 Promise.all을 실행한다면,

const order2 = () => {
	return new Promise((res, rej) => {
    	setTimeout(() => {
        	rej("에러~")
        }, 3000);
    })
};


실패되었다 뜨고, 어떠한 데이터도 얻지 못하니 주의해야한다! (가져온 데이터 순으로 화면에 그려주는 것조차 불가능하다!, rej의 값조차 안나옴 ㅋㅋ)

🙋🏻‍♂️ 하나의 정보라도 누락이 된다면, 페이지를 보여주면 안될 경우에 사용할 수 있다!

예제4 // Promise.race

Promise.race([order2(), order2(), order3()]).then((res) => {
	console.log(res);
});

사용방법은 all과 동일하다. all과 race의 차이점은 all을 전부 끝나야 보여준다면, race는 경주라는 뜻에 맞게 하나라도 먼저 끝나는것이 있으면 바로 끝낸다는 차이가 존재한다.

// 1번 주문 끝남여!

예제 2에서 1번 주문은 1초가 걸리기 때문에 가장 먼저 끝이난다.
따라서 2번 주문에 reject가 걸려있어도 콘솔에는 보이지 않는다.

profile
개인 이력, 포폴 관리 및 기술 블로그 사이트 👉 https://aimzero-web.vercel.app/

0개의 댓글