비동기 2편 Promise?

Msik·2022년 6월 5일
1

비동기 제어를 다루는 첫번째 방법은 callback 함수였다. 하지만 콜백함수의 단점이 나타나면서 이를 보완하기 위해 Promise라고 하는 문법을 만들게 되었다. 지금부터는 Promise의 탄생 이유를 잘 살펴보려한다.

대부분 Promise는 콜백지옥을 벗어나기 위해 만들어진 문법이라 말하지만 이것만이라고 말한다면 그것은 조금 부족하다라고 생각한다.

Promise는 콜백함수에서의 불확실한 신뢰성과 믿음을 극복하고 제어의 역전을 다시 되역전시킬수 있다는 것이 포인트라고 생각한다.

  • 그럼 콜백지옥이 왜 생기는 것일까에 대해 먼저 생각을 해보자!
  • 기존에 콜백함수의 문제는 그 리턴되는 값의 순서를 보장받을 수 없는 구조였다. 예를들어 처리가 빨리 끝나게 되면 앞선 비동기 함수에 비해 뒤에 호출되었어도 콜백함수가 먼저 테스크 큐에 등록되어 예상치 못하게 먼저 실행될 수 있는 구조였다.
    (A-B-C로 호출되게 코드를 짰는데 B-A-C로 호출될 수 있음.)

  • 마찬가지로 비동기 처리가 너무 오래 걸리면 가장 늦게 콜백함수가 테스크큐에 입력될 것이다. 우리는 이런 구조를 막기위해 즉 콜백함수의 결과를 동기적으로 받기위해(순서를 보장받기 위해) 콜백함수 안에 다른 콜백함수 또 그안에 다른 콜백함수를 넣는 형태로 콜백 지옥을 만들게 되었다.

  • 이러한 형태는 콜백함수 호출의 순서는 보장받을 수 있지만 코드의 가독성을 떨어트릴뿐더러 디버깅도 힘들고 코드가 복잡해지게 만들어 버리는 문제가 있다. 추가적으로 에러 처리에 대응하기 위한 try,catch,finally의 적용도 쉽지 않다.

  1. 불확실한 믿음을 주는 콜백함수 - 제어의 역전
    콜백지옥을 경험하게 된다면 우리는 어떻게 결과가 나올지 예상할 수 없고 그저 써드파티 라이브러리의 작동에 의존하게 된다. 즉 우리가 만든 프로그램이지만 그 제어가 다른 비동기처리를 하는 라이브러리에게 대부분 넘어가게 된다는 의미인데 결국 언제 어디서 에러가 날지도 모르는데 어떤 결과가 나오건 콜백함수의 작동을 우리가 통제할 수 없다는 것을 의미한다. 이런 문제를 극복하기 위해선 방어수단의 대책들을 코드에 추가하게 될 것이고 쓸데없이 많은 내용을 추가하는 코딩을 하게 될 것이다.

  2. 신뢰성을 되찾아오자 Promise - 제어의 되역전
    그럼 Promise를 사용하면 어떤 점이 달라질까 한가지 확실한건 Promise를 쓴다고 해서 비동기 처리시간이 달라지진 않는다. 처리의 주체 또한 여전히 다른 라이브러리에게 있을 것이다. 하지만 우리는 적어도 결과를 받기를 약속했고 그 결과의 상태에 따라서 다음 내용을 어떻게 진행할지 이끌어 갈 수 있다. 다시 제어의 중심을 가져왔다고 할 수 있는 것이다. 또한 Promise chaning을 사용하게 되면 콜백지옥보다 나은 방식으로 순서를 보장받으면서 코딩을 하는 것이 가능해진다.

3.Promise는 4가지 극복이 가능하다.

1) (함수 A가 끝나기도 전에 동기적으로) 콜백을 너무 일찍 부른다.
-- promise는 무조건 비동기적인 처리를 수행함으로 처리방식을 통일시킬 수 있다.
2) 콜백을 너무 늦게 부른다.
-- 순서를 보장 받기 위해 콜백지옥의 형태가됨. promise changing으로 개선
3) 한번도 콜백을 부르지 않는다.
-- promise.race로 극복가능
4) 콜백을 너무 많이 부른다.
-- 아래 예시를 보자 promise는 무조건 첫번째 귀결된 값을 반환한다.

1초마다 value 값을 1씩 증가시키고 value를 귀결시키고자 시도하는 프로미스
const promise = new Promise(function(resolve, reject) {
  let value = 0;
  setInterval(function() {
    value++;

    console.log(value, "value in Promise");

    resolve(value);
  }, 1000);
});

// 프로미스의 귀결 값을 1초마다 로깅한다.
setInterval(function() {
  promise.then(function(value) {
    console.log(value, "value in then callback");
  });
}, 1000);

// 그러나 프로미스 귀결 값은 최초의 귀결 이후 바뀌지 않는다. 

// 1 value in Promise
// 1 value in then callback
// 2 value in Promise
// 1 value in then callback
// 3 value in Promise
// 1 value in then callback
// 4 value in Promise
// 1 value in then callback
// 5 value in Promise
// 1 value in then callback
// ......

* 작동 메커니즘

  1. 프로미스 객체는 executor라는 콜백함수를 인수로 받는데 그 안에는 또 resolve와 reject라고 하는 콜백함수를 받게 된다.
  2. 결과적으로 프로미스 객체는 응답을 성공적으로 받으면 resolve(res)함수를 호출하고 오류를 받게 되면 reject(rej)함수를 호출하게 된다.
  3. 이때 이 두함수에 각각 인수로 전달된 res와 rej가 후에 프로미스 객체의 메서드 then과 catch를 사용할때 그 안에 콜백함수의 인수로 전달이 된다.

*요약

  • promise 시작 시 : pending 상태 - 이때 바로 접근하면 제대로 된 값을 받을 수 없다.
  • promise 실행 후 성공 : fulfilled상태 - 이때 resolve 콜백함수 실행 인수를 then으로 전달!
  • promise 실행 후 실패 : rejected상태 - 이때 reject콜백함수 실행 인수를 catch로 전달!

then, catch메소드는 프로미스의 결과가 확정되면 그다음 내용을 보여준다.
(마치 동기적인 것처럼 결과가 나온 뒤 접근하게 된다.)


  • 결과가 성공이라면
    .then() 메소드를 실행시키고 자신의 콜백함수의 인자로 위에 프로미스의 resolve(res)에 인수로 들어간 값을 전달받게 된다.

    .then((res1)⇒console.log(res1))

    즉 res = > res1으로 되는 것이다.
    원래는 then()메소드는 2가지의 콜백함수를 받는데
    (1. 결과가 성공일 때 실행, 2. 결과가 실패일 때 실행) 이렇게 사용할 경우 만약 then메소드 안에서 에러가 발생한다면 에러를 잡을 수 없으므로 catch()메소드로 따로 처리하는게 좋다.

  • 결과가 실패라면
    .catch() 메소드를 실행시키고 자신의 콜백함수의 인자로 위의 프로미스의 reject(rej)에 인수로 들어간 값을 전달 받게 된다. then()과 catch()는 같이 작동할 수 없으므로 순서에 상관없이 에러가 발생한다면 중간에 then을 스킵하고 catch메소드를 호출하게 된다.—then들의 마지막에 catch를 위치시켜야 catch가 모든 then의 에러를 잡을 수 있다. catch를 지난 then에서 에러가 나오면 못잡기 때문이다.

  • 추가적으로 finally()
    메소드는 결과가 어떻게 나오든 무조건 마지막에 실행시킬 수 있다.
    필수적으로 입력하고 싶은 내용이 있을 때 사용할 수 있다.

  • then과 catch, finally 메소드는 그 자체로 이미 프로미스를 반환하는데 이는 결과값을 또 다시 비동기로(then,catch,finally로 받을 수 있다는 것을 의미)사용할 수 이다.
    만약 안에 콜백함수가 프로미스를 반환하면 체이닝으로 연결시켜 작동할 수 있다.


-체이닝은 2가지 경우가 있다.

1.콜백함수의 결과가 일반 값일때: 

다음 then(res ⇒ res)의 콜백함수 인자 res는 바로 직전 then()의 콜백함수의 반환된 값이 넘겨진다.

2.콜백함수의 결과가 값이 프로미스일때:

.then((res)=>{console.log(res); return run(res+2)})      //1번 메소드
.then((res)=>{console.log(res); return run(res+2)})      //2번 메소드

1번 메소드에서 run함수로 다시 res+2를 넘겨주고 비동기로 작동하게 된다
그리고 그 결과 resolve와 reject가 반환하는 값을 2번 then()메소드가 받는다
profile
득근득근 개발!

0개의 댓글