프로미스(Promise)

BaeSeong-min·2025년 1월 4일
0

ES6 문법공부

목록 보기
18/22
post-thumbnail

📁Promise

📂Promise란?

비동기 작업을 처리하는데 사용되는 자바스크립트 객체이다. 비동기 작업이 맞이할 성공 혹은 실패를 나타낸다.

Promise 객체는 stateresult라는 두 가지 속성을 가진다.

Promise의 3가지 상태:

  • pending: 대기
  • fulfilled: 성공
  • rejected: 실패

🔄️ 비동기 작업이 진행 중일때 Promise 상태는 pending이다. 그리고, resultundefined이다.

✅ 비동기 작업이 성공적으로 완료되면, Promise 상태는 fulfilled이다.result비동기 작업이 반환하는 결과값이다. 서버에서 데이터를 받아오는 비동기 작업이라면 받아온 데이터가 Promiseresult가 된다.

⚠️ 비동기 작업이 진행 중에 문제가 발생해 실패하면, Promise 상태는 rejected이다.result 에는 에러 객체가 들어간다. 에러 객체를 보고 비동기 작업이 왜 실패했는지 알 수 있다.


📂Promise 사용해보기

const promise = new Promise((resolve, reject) => {
	console.log("비동기 작업");
});
// "비동기 작업"

Promise 생성자 함수를 호출할 때, 인자로 콜백함수를 넣어준다. 이때, 콜백함수를 executor이라고 불린다. executor 함수 안에 비동기 작업을 처리하면 된다.

new 키워드를 사용해서 Promise 객체가 생성되는 즉시, executor 함수가 실행된다.

🗂️비동기 작업이 성공한 경우

const promise = new Promise((resolve, reject) => {
	setTimeout(() => {
      const data = {name: "성민"}; // 서버로부터 데이터를 받는데 1초가 걸림을 표현함.
      console.log("네트워크 요청 성공");
      resolve(data);
    }, 1000);      
});

setTimeout(() => {
  console.log(promise);
}, 2000);

// "네트워크 요청 성공"

데이터를 받아오는 비동기 작업이 성공하면, Promise 객체에게 비동기 작업이 성공했다고resolve() 함수를 사용해서 알려준다. resolve() 함수의 인자에는 비동기 작업의 결과물을 넣어준다. 위 코드에서는 data가 결과물이 된다.

🗂️비동기 작업이 실패한 경우

const promise = new Promise((resolve, reject) => {
	setTimeout(() => {
      const data = null;
      
      if(data) {
        console.log("네트워크 요청 성공");
        resolve(data);
      } else {
        reject("네트워크 문제!!");
      }
    }, 1000);      
});

setTimeout(() => {
  console.log(promise);
}, 2000);

서버에서 데이터를 받아오다가, 문제가 생겨서 datanull이 되었다고 가정하자. reject() 메서드를 사용해서 Promise에게 비동기 작업이 실패했다는 것을 알려준다.reject() 메서드의 인자로는 비동기 작업이 왜 실패했는지 알려주는 에러 객체를 보편적으로 넣어준다.

Promise 객체가 생성되는 즉시, executor 함수가 실행된다. 만약, 특정 함수가 호출 됐을 때 비동기 작업을 시작하도록 만드려면 어떻게 해야할까? 즉, 비동기 작업을 수행하는 비동기 함수를 만드려면 어떻게 해야 할까?


📂비동기 함수 만들기

function getData() { // 비동기 함수
  const promise = new Promise((resolve, reject) => {
	setTimeout(() => {
      const data = {name: "성민"};
      
      if(data) {
        console.log("네트워크 요청 성공");
        resolve(data);
      } else {
        reject("네트워크 문제!!");
      }
    }, 1000);      
});
  
  return promise;
}

const promise = getData(); 
// "네트워크 요청 성공"

getData() 함수 안에서 Promise 객체를 생성한다. 이렇게 되면, getData() 함수가 호출되면 Promise 객체가 만들어지고, executor 함수가 호출되면서 비동기 작업이 수행된다.

비동기 작업을 수행하는 비동기 함수를 호출하는 입장에서 비동기 작업이 어떻게 진행되고 있는지 (비동기 작업이 아직 진행 중인지, 성공했는지, 실패했는지) 파악하기 위해 Promise 객체를 반환한다.


📂비동기 함수 사용하기 - then, catch, finally

Promise 객체를 반환하는 비동기 함수를 사용하는 입장에서, 비동기 함수를 의미있게 사용하기 위해서는 비동기 작업이 다 끝날 때까지 기다릴 수 있어야한다. 비동기 작업이 성공적으로 완료됐다면 그에 따른 처리. 실패하면 적절한 에러처리를 해주면 된다. 이 모든 것을 비동기 함수로부터 전달받은 Promise 객체를 사용해서 간편하게 가능하다.

Promise 객체는 then(), catch(), finally() 메서드를 가진다. 이 메서드를 사용해서 비동기 작업에 대한 후처리를 간편하게 할 수 있다.

🗂️비동기 작업이 성공한 경우

function getData() {
  const promise = new Promise((resolve, reject) => {
	setTimeout(() => {
      const data = {name: "성민"};
      
      if(data) {
        console.log("네트워크 요청 성공");
        resolve(data);
      } else {
        reject("네트워크 문제!!");
      }
    }, 1000);      
});
  
  return promise;
}

getData().then((data) => {
  const name = data.name;
  console.log(`${name}님 안녕하세요.`); // "네트워크 요청 성공" "성민님 안녕하세요."
});

then() 메서드는 비동기 작업이 완료될 때까지 기다렸다가, Promise가 fulfilled 상태로 바뀌면 후처리를 해줄 콜백 함수를 호출한다.

then()콜백함수의 매개변수는 Promise 객체의 result를 받는다. 다시 말해, resolve() 함수가 인자로 받은 데이터를 매개변수로 받는다.

🗂️비동기 작업이 실패한 경우

function getData() { 
  const promise = new Promise((resolve, reject) => {
	setTimeout(() => {
      const data = null;
      
      if(data) {
        console.log("네트워크 요청 성공");
        resolve(data);
      } else {
        reject("네트워크 문제!!");
      }
    }, 1000);      
});
  
  return promise;
}

getData().then((data) => {
  const name = data.name;
  console.log(`${name}님 안녕하세요.`);
}).catch((error) => {
  console.log("멋지게 에러처리를 했어요.");
});

// "네트워크 요청 성공" "멋지게 에러처리를 했어요."

비동기 작업을 처리하다가 중간에 문제가 생겨 실패하면 catch() 메서드의 콜백함수가 실행된다.

📝 비동기 작업 후처리 흐름

비동기 작업을 수행하는 getData() 함수를 호출하면, Promise 객체를 반환하고 비동기 작업이 성공해서 Promise 객체의 상태가 fulfilled가 되면 then() 메서드의 콜백함수가 호출되고, 비동기 작업 도중 문제가 발생하면 Promise 객체의 상태가 rejected가 되어 catch() 메서드의 콜백함수가 호출된다.

🗂️비동기 작업이 성공하든 실패하든 무조건 실행되야 하는 경우

function getData() { 
  const promise = new Promise((resolve, reject) => {
	setTimeout(() => {
      const data = { name: "성민" };
      
      if(data) {
        console.log("네트워크 요청 성공");
        resolve(data);
      } else {
        reject("네트워크 문제!!");
      }
    }, 1000);      
});
  
  return promise;
}

getData().then((data) => {
  const name = data.name;
  console.log(`${name}님 안녕하세요.`);
}).catch((error) => {
  console.log("멋지게 에러처리를 했어요.");
}).finally(() => {
  console.log("마무리 작업.");
});

// "네트워크 요청 성공" "성민님 안녕하세요." "마무리 작업."

finally() 메서드는 비동기 작업이 성공하든 실패하든 무조건 호출된다. 위 코드에선 비동기 작업이 성공했으므로, then()이 실행되고 그 다음에 finally()가 실행된다.


📂Promise Chaining

promise.then()은 항상 새로운 프로미스를 반환해준다. 따라서 then() 뒤에 또 다른 then()을 연결해줄 수 있다. 이렇게 체인 형태로 then() 메서드를 연결하는 기법을 프로미스 체이닝이라고 한다. 프로미스 체이닝을 이용하면 여러 개의 비동기 작업을 순서대로 수행하면서 가독성이 높아지고, 에러도 쉽게 관리 가능하다.

🗂️then 메서드의 콜백함수가 프로미스를 반환하는 경우

function getData() { 
  const promise = new Promise((resolve, reject) => {
	setTimeout(() => {
      const data = { name: "성민" };
      
      if(data) {
        console.log(`${name}님 안녕하세요.`);
        resolve(data);
      } else {
        reject("네트워크 문제!!");
      }
    }, 1000);      
});
  return promise;
}

const promise = getData();
promise
   .then((data) => {
   	 console.log(data);
   	 return getData();
  })
   .then((data) => {
  	 console.log(data);
   	 return getData();
  }) 
   .then((data) => {
  	 console.log(data);
   	 return getData();
  });

// (코드를 실행하고 1초 후에)
// "성민님 안녕하세요." { name: "성민" }
// (1초 후에)
// "성민님 안녕하세요." { name: "성민" }
// (1초 후에)
// "성민님 안녕하세요." { name: "성민" }

📝깔끔하게 문법을 생략했을 때의 코드

function getData() { 
  const promise = new Promise((resolve, reject) => {
	setTimeout(() => {
      const data = { name: "성민" };
      
      if(data) {
        resolve(data);
      } else {
        reject("네트워크 문제!!");
      }
    }, 1000);      
});
  return promise;
}

const promise = getData();
promise
   .then((data) => getData())
   .then((data) => getData()) 
   .then((data) => getData());
   .then(console.log); // { name: "성민" }

프로미스 체이닝을 위해서 then() 메서드의 콜백함수에서 Promise 객체를 반환했다.

🗂️then 메서드의 콜백함수가 값을 반환하는 경우

function getData() { 
  const promise = new Promise((resolve, reject) => {
	setTimeout(() => {
      const data = { name: "성민" };
      
      if(data) {
        resolve(data);
      } else {
        reject("네트워크 문제!!");
      }
    }, 1000);      
});
  return promise;
}

const promise = getData();
promise
   .then((data) => "hello")
   .then((data) => {
     console.log(data); // { name: "성민" }
   });

then() 메서드의 콜백함수에서 문자열과 같은 값을 반환하더라도 then()은 항상 Promise를 반환한다. 따라서, 반환된 값은 자동으로 Promise.resolve()로 감싸진다. 그 후, 곧바로 fulfilled 상태이면서 result는 반환해준 값인 Promise 객체가 반환된다.

그리고, 프로미스 체이닝을 이용하여 반환된 프로미스를 then()으로 후처리 해줄 수 있다.

🗂️then 메서드의 콜백함수가 예외를 던지거나 오류가 발생하는 경우

then() 메서드의 콜백함수가 예외를 던지거나, 오류가 발생하면 then() 메서드는 상태가 rejected이고 결과값은 에러 객체인 새로운 프로미스를 반환한다.

const promise = Promise.resolve(42);

promise
  .then(result => {
    console.log(result); // 42
    throw new Error('Something went wrong'); // 예외 발생
  })
  .then(() => {
    // 실행되지 않음
  })
  .catch(error => {
    console.error(error.message); // "Something went wrong"
  });

🗂️then 메서드의 콜백함수가 아무것도 반환하지 않는 경우

자바스크립트에서 함수가 return 문을 명시적으로 사용하지 않으면, undefined가 자동으로 반환된다. 따라서, then() 메서드는 상태가 fulfilled이고, 결과값이 undefined인 새로운 프로미스 객체를 반환한다.


📂Promise를 사용하는 Web API

fetch()Promise를 반환하는 Web APIs 중 하나이다. fetch()는 특정 URL로 네트워크 요청을 보내는 비동기 함수이다. 특정 서버로부터 데이터를 받아오는 일을 할 때 많이 사용된다.

fetch("https://jsonplaceholder.typicode.com/...")
  .then((response) => {
    return response.json();
})
  .then((data) => {
    console.log(data);
})
  .catch(error => {
    console.log("에러가 발생했습니다.");
})
  .finally(() => {
    console.log("마무리 작업");
});

서버로부터 받은 데이터는 Response 객체이므로, 데이터를 직접 사용할 수 있는 형태가 아니다. 따라서, 데이터를 사용하려면 적절한 메서드를 호출하여 형태를 변환해야 한다.

서버로부터 받아온 데이터 객체를 JSON 형식으로 변환하기 위해서 json() 메서드를 사용한다. 이때, json()Promise 객체를 반환한다. 따라서, 변환된 데이터를 then()에서 사용할 수 있다. 만약, Promise 객체의 상태가 rejected일 경우, then()은 실행되지 않고 건너뛰어서 catch()를 실행한다.

📝Response 객체의 메서드

  • response.json(): JSON 형식으로 변환.
  • response.text(): 텍스트 형식으로 변환.
  • response.blob(): 바이너리 형식(이미지, 파일 등)으로 변환.
  • response.arrayBuffer(): ArrayBuffer 형식으로 변환.
  • response.formData(): FormData 객체로 변환.

📂온라인 쇼핑몰 예제 Promise로 바꾸기

비동기 작업 간에 순서를 지정해주기 위해서, 비동기 콜백을 사용할 수 있다.
하지만, 이는 콜백 지옥을 일으킨다. 비동기 콜백 안에서 비동기 함수를 호출하는 코드를 생각해보면, 비동기 함수를 호출할 수록 중첩되는 콜백 함수가 늘어나기 때문이다.

따라서, 프로미스 체이닝을 사용해서 비동기 작업 간의 순서를 지정해주자.

// 1. 로그인
function login(username) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(username) {
        resolve(username);
      } else {
        reject(new Error("아이디를 입력해주세요."));
      }
    }, 1000);
  });
}

// 2. 장바구니에 넣기
function addToCart(product) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(product) {
        resolve(product);
      } else {
        reject(new Error("장바구니에 넣을 상품이 없어요!"));
      }
    }, 1000);
  }             
                     
// 3. 결제하기
function makePayment(cardNumber, product) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if(cardNumber.length !== 16) {
          reject(new Error("잘못된 카드 번호 입니다."));
          return;
        } 
        
        if(!product) {
          reject(new Error("결제할 상품을 넣어주세요"));
          return;
        }
        resolve(product);
      }, 1000);
    });
  }

  login("성민")
    .then((username) => {
      console.log(`${username}님 환영합니다.`);
      return addToCart("감자")
  })
    .then((product) => {
      console.log(`${product}를 장바구니에 넣었어요!`);
      return makePayment("0000000000000000", product)
  })
    .then((product) => {
      console.log(`${product} 결제를 완료했습니다.);
});

// (1초 뒤) "성민님 환영합니다." (1초 뒤) "감자를 장바구니에 넣었어요!" (1초 뒤) " 감자 결제를 완료했습니다."

📂catch 단계별로 에러처리하기

각 단계별로 다른 방식으로 에러를 처리해주고 싶다면 catch()를 체이닝 해주면 된다. catch()도 항상 Promise를 반환하기에 가능하다.

login("")
  .catch(() => {
  	  return "익명";
  })
  .then((username) => {
      console.log(`${username}님 환영합니다.`);
      return addToCart("")
  })
  .catch(() => {
  	  return "옥수수";
  })
  .then((product) => {
      console.log(`${product}를 장바구니에 넣었어요!`);
      return makePayment("0000000000000000", product)
  })
  .then((product) => {
      console.log(`${product} 결제를 완료했습니다.);
  })
  .catch((error) => {
  	  console.log(error.message);
  })
  .finally(() => {
	  console.log("마무리 작업");
  });

// (1초 뒤) "익명님 환영합니다." (1초 뒤) "옥수수를 장바구니에 넣었어요!" (1초 뒤) "옥수수 결제를 완료 했습니다." "마무리 작업"

첫 번재 catch()에서 값을 반환해주었다. 따라서, "익명"이라는 값이 자동으로 Promise.resolve()로 감싸져서 "익명"을 결과값으로 가지며, fulfilled 상태의 Promise 객체를 생성한다. 따라서 바로 다음 then()이 호출된 것이다.

장바구니에 넣는 비동기 작업이 실패 했다면, 옥수수를 장바구니에 넣어주도록 에러 처리 하였다.

프로미스 체인 중간 중간에 오류가 난 작업에 따라 다르게 에러 처리를 해주는 catch()를 넣어주면 중간에 문제가 발생해도 다음 then()으로 넘어가는 로직을 만들 수 있다.

📝깔끔하게 문법을 생략했을 때의 코드

login("")
  .catch(() => "익명")
  .then((username) => addToCart(""))
  .catch(() => "옥수수")
  .then((product) => makePayment("0000000000000000", product))
  .then(console.log)
  .catch((error) => console.log(error.message))
  .finally(() => console.log("마무리 작업"));

화살표 문법을 생략하고, 필요없는 출력을 지우고 나니 코드에 간단해진 것을 볼 수 있다. 비동기 콜백에 비해 가독성이 훨씬 좋다.

profile
성민의 개발 블로그 🔥

0개의 댓글