Promise

Goody·2021년 3월 3일
2

자바스크립트

목록 보기
10/13
post-thumbnail

Promise란?

비동기적 코드가 많아지면서 기하급수적으로 늘어나는 콜백 때문에

코드 전체의 로직을 가늠하기 어려운 경우가 많다.

이를 해결하기 위해 Promise가 등장했다.

💡 Promise는 콜백을 예측 가능한 패턴으로 사용할 수 있게 한다.


기본 개념

Promise 기반 비동기적 함수를 호출하면 그 함수는 Promise 인스턴스를 반환한다.

Promise는 성공(fulfilled) 하거나, 실패(rejected) 하거나 단 두가지 뿐이다.

즉, Promise는 성공 혹은 실패 둘 중 하나만 일어난다고 확신할 수 있다.

💡 성공, 혹은 실패로 결정된(settled) Promise 의 상태는 변할 수 없다.


Promise는 클래스이다

const promiseInstance = new Promise((resolve, reject) => {
		// executor 콜백 함수 내부
});

Promise 는 클래스이므로 new 키워드로 객체를 생성해야 한다.

생성자 함수의 인자로 executor 콜백 함수를 받는다.

executor 콜백 함수는 다시 resolve 콜백 함수와 reject 콜백 함수를 인자로 받는다.

💡 Promise의 인스턴스가 생성되는 순간 executor 함수 내부가 실행된다.


resolve , reject

위에서 resolve와 reject가 executor 콜백 함수의 인자로 들어간 것을 떠올려보자.

resolve(값)에 들어간 은 Promise의 인스턴스에서 then 메서드의 인자로 활용 가능하다.

예시를 보자.

const promiseInstance = new Promise((resolve, reject) => {
    					  // pending..
    					  // pending..
    					  // pending..
		resolve("goody"); // fulfilled 
});
promiseInstance.then((result) => console.log(result));	

// "goody"

💡 resolve의 인자로 들어간 "goody"promiseInstance.then 의 콜백함수에 인자로 들어간 것을 확인할 수 있다.


이번엔 executor 콜백 함수에 resolve 대신 reject를 넣어보자.

const promiseInstance = new Promise((resolve, reject) => {

	reject(new Error("something wrong!")); // rejected 
});
promiseInstance.catch((result) => console.error(result));	

// Error: something wrong!

Promise 의 인스턴스에서 then 대신 catch 를 썼다는 것 빼고는 당장은 별다른 차이가 없어 보인다.

그러나 resolve와 reject는 Promise Chaining에서 빛을 발한다.


Promise Chaining

처음에 봤던 코드를 다시 보자.

const promiseInstance = new Promise((resolve, reject) => {

		resolve("goody"); // fulfilled 
});
promiseInstance.then((result) => console.log(result));	

// "goody"
// 반환 값: Promise (fulfilled)

promiseInstance 는 Promise 클래스의 객체이다.

따라서 Promise가 갖고있는 then, catch 등의 메서드를 사용할 수 있다.

그런데 then이나 catch는 또 다른 Promise 를 리턴하는 메서드이다.

그렇다면 Promise 뿐 만 아니라 then도 then을 가질 수 있다.

then().then().then() 이런 식으로 체이닝이 가능하다는 이야기이다.


const workingPromise = new Promise((resolve, reject) => {
    // executor 내부에는 통상 네트워크 요청 등 작업 시간이 오래 걸리는 비동기 로직이 들어가므로, 임의로 setTimeout을 넣었다.
    setTimeout(() => resolve(1), 1000);
});

workingPromise
    .then(result => result * 2)
    .then(result => result * 3)
    .then(result => {
   		console.log(result); 
	});

// 6

1 -> 1 * 2 -> 2 * 3 -> 6

executor 내부의 resolve() 에는 1이 전달되었고,

체이닝을 거치며 최종 result 에는 6이 저장되었다.


에러 잡아내기 예시 : 햄버거 만들기

햄버거를 만드는 과정은 아래와 같다(고 가정하자).

  • 🐮 데려오기

  • 🥩 가공하기

  • 🍔 ......완성!

    🐮 없이는 🥩를 만들 수 없고, 🥩 없이는 🍔 를 만들 수 없다.


이를 Promise로 표현하면,

const getCow = () =>
    new Promise((resolve, reject) => {
        setTimeout(() => resolve(`🐮`), 1000); // 소를 데려오기 1초
    });
const getMeat = (cow) =>
    new Promise((resolve, reject) => {
        setTimeout(() => resolve(`${cow} => 🥩`), 1000);	// 도축 1초
    });
const getBurger = (meat) =>
    new Promise((resolve, reject) => {
        setTimeout(() => resolve(`${meat} => 🍔`), 1000);	// 햄버거 쌓기 1초
    });

💡 getMeat 공정은 소를, getBurger 공정은 고기를 필요로 하고 있다.


당연한 소리겠지만, 햄버거를 만드려면 🐮를 데려오는 데에서 부터 시작해야한다.

공정의 순서는 아래와 같다.

getCow()					// 소를 데려온다.
    .then(result => getMeat(result))		// 데려온 result 를 고기로 만든다.
    .then(result => getBurger(result))		// 고기가 된 result로 버거를 만든다.
    .then(result => console.log(result))	// 결과를 출력한다.

//  결과 : 🐮 => 🥩 => 🍔

에러 발생!

햄버거를 만드는 과정이 위처럼 순탄하기만 하면 좋겠지만,

getMeat 공정에서 에러가 발생했다고 해보자.

const getCow = () =>
    new Promise((resolve, reject) => {
        setTimeout(() => resolve(`🐮`), 1000); 
    });
const getMeat = (cow) =>
    new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error (`error! ${cow} => 🥩`)), 1000);	
    });
const getBurger = (meat) =>
    new Promise((resolve, reject) => {
        setTimeout(() => resolve(`${meat} => 🍔`), 1000);	
    });


getCow()					// 소를 데려온다.
    .then(result => getMeat(result))		// 데려온 result 를 고기로 만든다.
    .then(result => getBurger(result))		// 고기가 된 result로 버거를 만든다.
    .then(result => console.log(result))	// 결과를 출력한다.
	.catch(console.log)

// Error: error! 🐮 => 🥩

공정에 문제가 없었다면 햄버거가 완성되어야 하지만,

소에서 고기를 얻는 getMeat 함수에서 reject 가 발생했다.

이를 첫 번째 then => catch 에서 받아서 결과를 출력했으므로,

두 번째 then 함수 내부의 getBurger 가 실행되지 못한 것이다.


에러를 처리한 햄버거 공정

const getCow = () =>
    new Promise((resolve, reject) => {
        setTimeout(() => resolve(`🐮`), 1000); // 소를 데려오기 1초
    });

const getMeat = (animal) =>
    new Promise((resolve, reject) => {
        setTimeout(() => {
            if (animal === '🐮') resolve(`🥩`);
            else reject(new Error(`error! ${animal} => 🥩`));
        }, 1000) 	                        // 도축 1초
    });

const getBurger = (meat) =>
    new Promise((resolve, reject) => {
        setTimeout(() => {
            if (meat === '🥩') resolve(`🍔`);
            else reject(new Error(`error! ${meat} => 🍔`));
        }, 1000) 	                        // 햄버거 제조 1초
    });

    getCow()					            // 소를 데려온다.
    .then(result => getMeat(result))		// 데려온 result 를 고기로 만든다.
    .then(result => getBurger(result))		// 고기가 된 result로 버거를 만든다.
    .then(result => console.log(result))	// 결과를 출력한다.
    .catch(console.log)

animal 에 🐮 대신 🐷 가 들어간다면?


마치며

디버깅의 생명은 에러가 생긴 곳을 알아내는 것이다.

Promise 와 같은 비동기 함수는
코드의 흐름을 따라가기 쉽지 않으므로,

위처럼 에러처리를 분명하게 해주어야 시간을 절약할 수 있을 것이다.


REFERENCE

드림코딩by 엘리 : https://www.youtube.com/watch?v=JB_yU6Oe2eE&t=1452s

러닝 자바스크립트 ES6 (이선 브라운 저)

4개의 댓글

comment-user-thumbnail
2021년 3월 3일

정리 완전 깔끔하네요! 영상보고 글을 다시보니 더 분명해집니다!

1개의 답글
comment-user-thumbnail
2021년 3월 12일

reject 속성은 사실 잘 써보지 않았었는데 이해가 되는 느낌이에요!!

1개의 답글