[js] Promise

비트·2023년 6월 2일
0

JavaScript

목록 보기
12/22
post-thumbnail

Promise

Promise는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체

기본적으로 promise는 함수에 콜백을 전달하는 대신에, 콜백을 첨부하는 방식의 객체

  • 프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있다.
    • 다만 최종 결과를 반환하는 것이 아니고, 미래의 어떤 시점에 결과를 제공하겠다는 약속(프로미스)을 반환

프라미스 객체의 state, result 프로퍼티는 내부 프로퍼티이다.


state

처음엔 pending(보류)이었다 resolve가 호출되면 fulfilled, reject가 호출되면 rejected로 변한다.

  • 대기(pending) : 이행하지도, 거부하지도 않은 초기 상태.
  • 이행(fulfilled) : 연산이 성공적으로 완료됨.
  • 거부(rejected) : 연산이 실패함.



Promise() 생성자

Promise 생성자는 주로 프로미스를 지원하지 않는 함수를 감쌀 때 사용한다.

  • new Promise(resolve,reject)

result

처음엔 undefined이었다 resolve(value)가 호출되면 value로, reject(error)가 호출되면 error로 변한다.


resolve


reject




인스턴스 메서드

then

  • Promise.prototype.then()
    • then() 메서드는 Promise를 리턴하고 두 개의 콜백 함수를 인수로 받는다.
    • 하나는 Promise가 이행했을 때, 다른 하나는 거부했을 때를 위한 콜백 함수이다.
    • promise.then(
        function(result) { /* 결과(result)를 다룹니다 */ },
        function(error) { /* 에러(error)를 다룹니다 */ }
      );

  • 예시

    • let promise = new Promise(function(resolve, reject) {
        setTimeout(() => resolve("완료!"), 1000);
      });
      
       // resolve 함수는 .then의 성공 여부에 따라 함수(인수)를 실행한다.
      promise.then(
        result => alert(result), // 성공적으로 이행된 경우, 1초 후 "완료!"를 출력
        error => alert(error) // 거부된 경우, 1초 후 "Error: 에러 발생!"을 출력
      );
    • // 작업이 성공적으로 처리된 경우만 다루고 싶다면 .then에 인수를 하나만 전달하면 된다.
      
       let promise = new Promise(resolve => {
        setTimeout(() => resolve("완료!"), 1000);
      });
      promise.then(alert); // 1초 뒤 "완료!" 출력

catch

  • Promise.prototype.catch()
    • catch() 메서드는 Promise가 거부될 때 호출할 함수를 예약한다.
    • 에러가 발생한 경우만 다루고 싶다면 .then(null, errorFunction)같이 null을 첫 번째 인수로 전달하면 된다.
    • .catch.thennull을 전달하는 것과 동일하게 작동한다.

  • 예시

    • let promise = new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error("에러 발생!")), 1000);
      });
      
       // .catch(f)는 promise.then(null, f)과 동일하게 작동한다
      promise.catch(alert); // 1초 뒤 "Error: 에러 발생!" 출력

finally

  • Promise.prototype.finally()
    • finally() 메소드는 Promise가 처리되면 충족되거나 거부되는지 여부에 관계없이 지정된 콜백 함수가 실행된다.

  • 예시

    • new Promise((resolve, reject) => {
        /* 시간이 걸리는 어떤 일을 수행하고, 그 후 resolve, reject를 호출함 */
      })
        // 성공·실패 여부와 상관없이 프라미스가 처리되면 실행됨
        .finally(() => 로딩 인디케이터 중지)
        .then(result => result와 err 보여줌 => error 보여줌)
        
    • 그러나, finally.then(f, f)과 완전히 같진 않다.

      1. finally 핸들러엔 인수가 없다.
        • finally에선 프라미스가 이행되었는지, 거부되었는지 알 수 없다.
        • finally에선 절차를 마무리하는 ‘보편적’ 동작을 수행하기 때문에 성공·실패 여부를 몰라도 된다.
      2. finally 핸들러는 자동으로 다음 핸들러에 결과와 에러를 전달한다.
    • // result가 finally를 거쳐 then까지 전달된다.
      
       new Promise((resolve, reject) => {
        setTimeout(() => resolve("결과"), 2000)
      })
        .finally(() => alert("프라미스가 준비되었습니다."))
        .then(result => alert(result)); // <-- .then에서 result를 다룰 수 있음
    • // 프라미스에서 에러가 발생하고 이 에러가 finally를 거쳐 catch까지 전달된다.
      
       new Promise((resolve, reject) => {
        throw new Error("에러 발생!");
      })
        .finally(() => alert("프라미스가 준비되었습니다."))
        .catch(err => alert(err)); // <-- .catch에서 에러 객체를 다룰 수 있음



Promise chaining

두 개 이상의 비동기 작업을 순차적으로 실행해야 하는 상황에서,
각각의 작업이 이전 단계 비동기 작업을 성공하고 이후 그 결과값을 이용하여,
다음 비동기 작업을 실행해야하는 경우 promise chain을 이용한다.


예시

const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);

// 또는

const promise2 = doSomething().then(successCallback, failureCallback);

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);
  • 여러 비동기 작업을 연속적으로 수행하면 고전적인 지옥의 콜백 피라미드가 만들어졌다.

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback)
  • 모던한 방식으로 접근한다면, 콜백 함수들을 반환된 promisepromise chain을 형성하도록 추가할 수 있다.

  • then 에 넘겨지는 인자는 선택적(optional)이다.
    그리고, catch(failureCallback)then(null, failureCallback) 의 축약이다.


// 화살표 함수로 다음과 같이 표현이 가능하다.

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
  console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);



Promise.all()

여러 개의 비동기 작업을 동시에 처리하고 싶을 때 사용


let promise = Promise.all([...promises...]);
  • Promise.all은 요소 전체가 프라미스인 배열(엄밀히 따지면 이터러블 객체이지만, 대개는 배열)을 받고 새로운 프라미스를 반환한다.
  • 배열 안 프라미스가 모두 처리되면 새로운 프라미스가 이행되는데, 배열 안 프라미스의 결과값을 담은 배열이 새로운 프라미스의 result가 된다.

아래 Promise.all은 3초 후에 처리되고, 반환되는 프라미스의 result는 배열 [1, 2, 3]이 된다.

Promise.all([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
  new Promise(resolve => setTimeout(() => resolve(3), 1000))  // 3
]).then(alert); 
// 프라미스 전체가 처리되면 1, 2, 3이 반환된다. 
// 각 프라미스는 배열을 구성하는 요소가 된다.
  • Promise.all의 첫 번째 프라미스는 가장 늦게 이행되더라도 처리 결과는 배열의 첫 번째 요소에 저장된다.
    • 모든 프로미스가 이행되었을 때, Promise.all()이 결과를 반환하기 때문.

Promise.all에 전달되는 프라미스 중 하나라도 거부되면, Promise.all이 반환하는 프라미스는 에러와 함께 바로 거부된다.

Promise.all([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("에러 발생!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: 에러 발생!
  • 2초 후 두 번째 프라미스가 거부되면서 Promise.all 전체가 거부되고, .catch가 실행된다.
  • 거부 에러는 Promise.all 전체의 결과가 된다.

에러가 발생하면 다른 프라미스는 무시된다.

프라미스가 하나라도 거부되면 Promise.all은 즉시 거부되고, 배열에 저장된 다른 프라미스의 결과는 완전히 무시된다.
이행된 프라미스의 결과도 무시된다.

profile
Drop the Bit!

0개의 댓글