[JavaScript] 비동기 처리 - Promise

 

 

앞서 콜백 지옥이라는 콜백 함수를 중첩해서 사용하여 가독성과 유지 보수성이 떨어지는 문제점이 발생하였다.

이 문제를 해결하기 위 Promise를 사용하여 콜백 지옥을 해결하며, 비동기 처리도 간결하게 작성할 수 있다.

 

 

Promise

콜백 지옥을 해결하기 위한 방안으로 ES6에 도입된 기능이다.

 

우선 Promise를 사용하기 위해 Promise 객체를 생성해야 한다.

const promise = new Promise((resolve, reject) => {
  ...
});

Promise는 new 키워드와 같이 생성자를 이용하여 생성한다.

이때 new Promise를 하는 순간 이곳에 할당된 비동기 작업은 바로 시작되게 된다.

이 생성자는 executor라는 실행함수를 인자로 받으며, executer 함수에는 resolve, reject라는 2개의 함수형 매개변수를 갖는다.

 

executor 함수 내부

    : 비동기 작업을 실행하는 코드가 작성

    : 해당 작업이 성공하였을 경우, 성공한 결과 값을 리턴하는 resolve 함수가 호출

    : 실패할 경우 에러 처리를 하는 reject 함수가 호출

 

resolve

    : 함수 안의 처리가 끝났을 때 호출해야 하는 콜백 함수

    : 어떤 값도 인수로 넘길 수 있다.

    : 다음 처리를 실행하는 함수에 전달된다.

 

reject

    : 함수 안의 처리가 실패했을 때 호출해야 하는 콜백 함수

    : 어떤 값도 인수로 넘길 수 있다.

    : 주로 오류 메시지 문자열을 인수로 사용한다.

 

 

 

then / catch 메서드

Promise가 끝나고 난 다음의 동작을 설정해주기 위해 then / catch 메서드를 사용한다.

 

then

    : 해당 Promise가 성공했을 때 동작을 지정

    : 인자로 함수를 받는다.

 

catch

    : 해당 Promise가 실패했을 때 동작을 지정

    : 인자로 함수를 받는다.

 

위 함수들은 chain 형태로 연속해서 호출할 수 있다.

 

then / catch 메서드를 사용한 예시를 보면서 이해가 쉬울 것이다.

const promise = new Promise((resolve, reject) => {
  resolve();
});

promise
  .then(() => {
    console.log("then 출력");
  })
  .catch(() => {
    console.log("catch 출력");
  });

위의 코드 실행 결과 resolve를 호출하였기 때문에 "then 출력"결과가 출력된다.

 

const promise = new Promise((resolve, reject) => {
  reject();
});

promise
  .then(() => {
    console.log("then 출력");
  })
  .catch(() => {
    console.log("catch 출력");
  });

다음 코드 실행 결과 reject가 호출되었으므로 "catch 출력"결과가 나올 것이다.

 

 

 

 

Promise의 3가지 상태

▶ Pending(대기)

    : new Promise 메서드를 호출하기만 한 상태

    : 비동기 처리 로직이 아직 완료되지 않은 상테

 

▶ Fulfilled(이행)

    : resolve가 호출된 상태

    : 비동기 처리가 완료되어 promise 결과 값을 반환해준 상태

 

▶ Rejected(실패)

    : reject가 호출된 상태

    : 비동기 처리가 실패하거나 오류가 발생한 상태

 

 

Promise로 비동기 처리 연결

function increase(number) {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      const result = number + 10;
      if (result > 50) {
        // result가 50보다 클 때 에러 발생시키게 하기기
        const e = new Error("Number is Too Big");
        return reject(e);
      }
      resolve(result);
    }, 1000);
  });
  return promise;
}

increase(0)
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .catch((e) => {
    console.log(e);
  });

.then을 사용하면 그 다음 작업을 설정하기 때문에 콜백 지옥이 형성되지 않는다.

위의 코드를 보면 then, catch에서 다시 다른 then이나 catch를 붙였다.

이전 then의 return 값을 다음 then의 매개변수로 넘긴다.

Promise를 return한 경우 Promise가 수행된 후 다음 then이나 catch를 호출하게 된다.

 

Promise.all

Promise.all을 사용하면 Promise 여러 개를 한 번에 실행할 수 있다.

이전 예시는 모두 비동기 처리 여러 개를 직렬로 연결해 순차적으로 실행하였다면, Promise.all을 이용하면 비동기 처리 여러 개를 병렬로 처리 가능하다.

 

Promise.all(iterable);

이때 iterable은 배열과 같이 반복 가능한 객체를 말한다.

 

function buySomething(name, nowMoney) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const pay = 500;
      const remain = nowMoney - pay;
      if (remain >= 0) {
        console.log(`${name} : ${pay}원 지불`);
        resolve(remain);
      } else {
        reject(`${name} : 잔액부족: 현재 잔액${nowMoney}원`);
      }
    }, 2000);
  });
}

Promise.all([
  buySomething("A", 500),
  buySomething("B", 1000),
  buySomething("C", 1500)
])
  .then(remain => {
    console.log(remain);
  })
  .catch(error => {
    console.log(error);
  });

위의 코드를 실행한 결과 remain은 [0, 500, 1000]으로 남게 될 것이다.

 

Promise.race

Promise.race는 가장 먼저 종료한 Promise 객체 결과만 다음 작업으로 보낼 때 사용한다.

function buySomething(name, nowMoney) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const pay = 500;
      const remain = nowMoney - pay;
      if (remain >= 0) {
        console.log(`${name} : ${pay}원 지불`);
        resolve(remain);
      } else {
        reject(`${name} : 잔액부족: 현재 잔액${nowMoney}원`);
      }
    }, 2000);
  });
}

Promise.race([
  buySomething("A", 500),
  buySomething("B", 1000),
  buySomething("C", 1500)
])
  .then(remain => {
    console.log(remain);
  })
  .catch(error => {
    console.log(error);
  });

Promise.race로 실행하면 A가 실행되고 남은 결과 값인 0만 반환될 것이다.

즉 buySomething("A", 500)이 가장 먼저 실행되며, 가장 먼저 종료되기 때문에 해당 작업의 결과 값만 반환된다.

하지만 나머지 작업도 실행은 될 것이다.

 

 

반응형