ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Section 14. 자바스크립트 프로미스
    자바스크립트 2023. 4. 10. 11:18

    어떤 로직을 구현하기 위해 콜백 함수를 계속 쓰다 보면 이런 형태와 같은 구조가 될 수 있다.

    setTimeout(() => {
      console.log(1);
      setTimeout(() => {
        console.log(2);
          setTimeout(() => {
            console.log(3);
            setTimeout(() => {
              console.log(4);
              setTimeout(() => {
                console.log(5);
              }, 500);
            }, 500);
          }, 500);
      }, 500);
    }, 500);

    콜백 함수의 콜백 함수의 콜백 함수... 이런 형태를 콜백 지옥 이라고 한다.

    콜백 지옥 callback hell

    연속적으로 비동기 코드를 써야 하는 경우

    • 위와 같이 콜백 함수 안에 또 다른 콜백 함수를 넣어야 하는 상황 발생 - 콜백 지옥
    • 횟수가 많을수록 가독성도 낮아지고 직관성이 떨어짐
    const DEADLINE = 1400;
    
    function relayRun (name, start, nextFunc, failMsg) {
      console.log(`👟 ${name} 출발`);
      const time = 1000 + Math.random() * 500;
    
      setTimeout(() => {
        if (time < DEADLINE) {
          console.log(`🚩 ${name} 도착 - ${(start + time)/1000}초`);
          nextFunc?.(start + time);
    
        } else {
          console.log(failMsg);
          console.log(`😢 완주 실패 - ${(start + time)/1000}초`);
        }
    
        if (time >= DEADLINE || !nextFunc) {
          console.log('- - 경기 종료 - -');
        }
      }, time);
    }
    relayRun('철수', 0, start1 => {
      relayRun('영희', start1, start2 => {
        relayRun('돌준', start2, start3 => {
          relayRun('정아', start3, start4 => {
            relayRun('길돈', start4, null, '아아, 아깝습니다...');
          }, '정아에게 무리였나보네요.');
        }, '돌준이 분발해라.');
      }, '영희가 완주하지 못했네요.');
    }, '철수부터 광탈입니다. ㅠㅠ');

    이전 콜백함수의 결과가 다음 콜백함수로 넘겨져 축적되는 형태이다. 

    한 사람이라도 데드라인을 넘기면 완주 실패 로그가 출력이 되고 사람마다 다른 실패 메시지가 출력된다.

    완주 실패시 전체 소요시간이 출력되고 실패든 성공이든 마지막에 --경기 종료 -- 메시지가 출력되게 된다.

    DEADLINE이 1400 (밀리초) 이고 time이 1500 (밀리초) 까지기 때문에 5분의 1확률로 실패할 수 있게 된다.

    요점은 이런 형태의 코드는 가독성이 매우 떨어지고 좋지 않다.

    프로미스 promise 

    • (보통 시간이 걸리는) 어떤 과정 이후 주어진 동작을 실행할 것이란 약속이다.
    • 중첩된 비동기 코드를 직관적이고 연속적인 코드로 작성할 수 있도록 한다.
    const borrow = 20;
    
    // 빌린 돈의 10%를 더해 값겠다는 약속
    // reject는 지금 사용하지 않음
    const payWith10perc = new Promise((resolve, reject) => {
      resolve(borrow * 1.1);
    });
    
    payWith10perc
    .then(result => {
      console.log(result + '만원');
    });

    payWith10perc 란 인스턴스를 Promise생성자 함수로 생성했다. 이 때 resolve와 reject 라는 인자를 받는다.

    resolve는 약속이 성공 했을 때 돌려주는 리턴값을 말하는 것이다. 

    즉, resolve라는 콜백 함수를 인자로 받고 resolve(borrow * 1.1) 함수를 실행한다는 것은 borrow의 10%를 더하겠다~ 는 일종의 약속을 만드는 것이다.

    Promise의 인스턴스 payWith10perc는 then이라는 매서드를 가지고 있다. 

    then 안에는 위에서 만든 약속을 (resolve 반환값) 넣어준다. 그럼 출력은 다음과 같다

    여기서 then 메서드는 또 다른 Promise 인스턴스를 반환하는 것을 볼 수 있다. 

     

    정리하면 다음과 같다

    생성자 Promise

    • 새로운 약속을 하는 코드
    • 인자로 받는 콜백함수의 첫 번째 인자 resolve - 약속 이행 성공시, 반환할 값 넣어 실행

     

    프로미스 인스턴스 (만들어진 약속) 의 then 메서드

    • resolve를 통해 (약속대로) 반환된 결과를 인자로 하는 콜백 함수를 넣는다.
    • 또 다른 프로미스를 반환 - 메서드 체이닝이 가능
    const borrow = 20;
    
    const payWith10perc = new Promise((resolve, reject) => {
      // 💡 내부에서 비동기 코드 사용
      setTimeout(() => {
        resolve(borrow * 1.1);
      }, 1000); // 1초 후 갚겠음
    });
    
    // ⚠️ 콘솔에서 분리해서 실행하면 안 됨!
    // 프로미스가 생성되는 순간부터 시간 경과
    payWith10perc
    .then(result => {
      console.log(result + '만원');
    });

    일반적으로 내부에 비동기 코드를 사용한다.

    시간이 소모되는 비동기 과정 후 ~를 반환하겠다는 약속이다.

     

    const borrow = 20;
    
    const payWith10perc = new Promise((resolve, reject) => {
      setTimeout(() => {
        if (Math.random() < 0.5) {
          // 💡 돈을 값을 수 없게 되었을 때
          reject('사업 망함'); // 보통 실패사유나 관련 설명을 넣음
        }
        resolve(borrow * 1.1);
      }, 1000); // 1초 후 갚겠음
    });
    
    payWith10perc
    .then(result => {
      console.log(result + '만원');
    }
    // 💡 두 번째 인자로 reject를 받는 콜백을 넣을 수 있지만
    // 아래처럼 catch로 진행하는 것이 더 깔끔함
    )
    .catch(msg => {
      console.error(msg);
    })
    .finally(() => {
      console.log('기한 종료');
    });

    위에서 설명했듯이 resolve의 반환값은 then으로 가서 실행된다. 

    하지만 약속이 reject를 만나게 되면 then은 무시되고 catch가 실행된다. 여기서 reject의 반환값이 catch의 인자로 들어가서 실행 된다.

    finally는 실패, 성공 유무과 관계없이 항상 실행된다. 

    생성자 Promise

    인자로 받는 콜백함수의 두 번째 인자 reject - 약속 이행 실패시, 반환할 값을 넣어 실행

    reject가 실행되면 resolve는 무시됨

     

    프로미스 인스턴스의 

    • catch 메서드 : reject를 통해 반환된 결과를 인자로 하는 콜백 함수를 넣음.
    • finally 메서드 : 성공 유무에 관계없이 실행할 콜백 함수 - 필요할 때만 사용한다.
    • then 과 더불어 메서드 체이닝으로 사용한다.

    그럼 맨 처음에 했던 예제를 프로미스로 구현해보면 다음과 같다

    const DEADLINE = 1400;
    
    function getRelayPromise (name, start, failMsg) {
      console.log(`👟 ${name} 출발`);
    
      // 💡 랜덤 시간만큼 달리고 결과를 반환하겠다는 약속을 만들어 반환
      return new Promise((resolve, reject) => {
        const time = 1000 + Math.random() * 500;
    
        setTimeout(() => {
          if (time < DEADLINE) {
            console.log(`🚩 ${name} 도착 - ${(start + time)/1000}초`);
            resolve(start + time);
          } else {
            
            console.log(failMsg);
            reject((start + time) / 1000);
          }
        }, time);
      })
    }
    getRelayPromise('철수', 0, '철수부터 광탈입니다. ㅠㅠ')
    .then(result => {
      return getRelayPromise('영희', result, '영희가 완주하지 못했네요.');
    })
    .then(result => {
      return getRelayPromise('돌준', result, '돌준이 분발해라.');
    })
    .then(result => {
      return getRelayPromise('정아', result, '정아에게 무리였나보네요.');
    })
    .then(result => {
      return getRelayPromise('길돈', result, '아아, 아깝습니다...');
    })
    .catch(msg => {
      console.log(`😢 완주 실패 - ${msg}초`);
    })
    .finally(() => {
      console.log('- - 경기 종료 - -');
    });

     

Designed by Tistory.