Promise 객체

Promise 객체

·

5 min read

Promise 객체

프로미스 객체의 필요성

프로미스 객체에 대해 이야기 하기전에 자바스크립트의 비동기 처리에 대해 다시 한 번 살펴보자.

앞서 setTimeout함수를 이용해 작업을 비동기적으로 처리했었고, setTimeout함수는 작성된 코드와 같이 콜백함수와 ms단위의 지연 시간을 매개변수로 입력받는 함수라고 배웠었다.

아래는 지난시간 작성했던 workA, workB, workC 함수를 비동기 함수로 다시 생성하고, workD는 동기적으로 작성한 모습이다.

const workA = (value, callback) => {
  setTimeout(() => {
    callback(value);
  }, 5000);
};
const workB = (value, callback) => {
  setTimeout(() => {
    callback(value);
  }, 3000);
};
const workC = (value, callback) => {
  setTimeout(() => {
    callback(value);
  }, 10000);
};
const workD = (value, callback) => {
    callback(value);
};

workA("workA", (res) => {
  console.log(res);
});
workB("workB", (res) => {
  console.log(res);
});
workC("workC", (res) => {
  console.log(res);
});
workD("workD", (res) => {
  console.log(res);
});

이번에는 이 함수들을 호출할 때, 출력할 단어와, 단어를 출력하는 함수를 인수로 넘겨주었다.

위 코드의 실행결과는 다음과 같다.

// workD
// workB
// workA
// workC


콜백지옥

이 코드를 다음과 같이 바꿔보자.

  1. workA에서 매개변수로 받은 값에 5를 더한다.
  2. 그 결과값을 workB에서 전달받아 3을 뺀다.
  3. 그 결과값을 workC에서 전달받아 10을 더한다.
const workA = (value, callback) => {
  setTimeout(() => {
    callback(value + 5);
  }, 5000);
};
const workB = (value, callback) => {
  setTimeout(() => {
    callback(value - 3);
  }, 3000);
};
const workC = (value, callback) => {
  setTimeout(() => {
    callback(value + 10);
  }, 10000);
};
const workD = (value, callback) => {
    callback(value);
};

workA(10, (resA) => {
  console.log(`1. ${resA}`);
  workB(resA, (resB) => {
    console.log(`2. ${resB}`);
    workC(resB, (resC) => {
      console.log(`3. ${resC}`);
    });
  });
});
workD("workD", (res) => {
  console.log(res);
});

자바스크립트에서 비동기 함수의 결과값을 또 다른 비동기 함수에서 사용하기 위해 위와같이 콜백함수 안에 콜백함수를 전달하는 방식을 사용하게 되면 다음과 같은 장점이 있다.

  • 함수의 실행 순서를 알기 쉽다.
  • 유연한 프로그래밍을 할 수 있다.

하지만, 동시에 가독성이 매우 좋지 않아 많은 오류를 발생시킬 수도 있는 것이 단점이다.

위와같이 >모양으로 복잡하게 생긴 코드를 콜백 지옥이라고 하는데, 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 과도하게 깊어지는 것을 의미한다.

이러한 콜백 지옥은 프로미스(Promise) 객체를 이용해 해결할 수 있다.

프로미스 객체

프로미스 객체는 비동기 작업을 더 편리하게 처리할 수 있게 도와주는 자바스크립트의 내장 객체이다.

생성방법

프로미스 객체는 new 키워드와 생성자를 사용해 생성할 수 있다.

const promise = new Promise();


프로미스 객체를 만들 때에는 인수로 executor 라는 실행함수를 전달하는데, 이 실행함수는 프로미스 생성자에 반드시 들어가야 하는 함수로 작업을 비동기로 처리한다.

const exeutor = (resolve, reject) => {
  // 코드
}

const promise = new Promise();


아래의 코드를 실행하면 '3초만 기다리세요'라는 문장이 출력되는 것을 확인할 수 있다.

const executor = (resolve, reject) => {
  setTimeout(() => {
    console.log('3초만 기다리세요');
  }, 3000)
}

const promise = new Promise();

이처럼 executor함수는 프로미스 객체를 생성함과 동시에 실행되는 실행함수이다.

resolve와 reject

executor의 매개변수인 resolvereject는 자바스크립트에서 자체적으로 제공하는 콜백함수이다. executor는 비동기처리가 성공하면 resolve를, 실패한다면 reject를 호출한다.

프로미스 내부 프로퍼티

프로미스 객체는 state, result 이렇게 두 가지의 내부 프로퍼티를 갖는다.

프로미스 객체는 맨 처음 대기(pending)상태와 undefined의 값을 가지고 있다가 executor가 호출하는 콜백함수에 따라 stateresult가 변화된다.

위 그림과같이 프로미스 객체의 상태는 resolvereject를 통해 변하는데, 한 번 변경된 상태는 더 이상 변하지 않기 때문에 처리가 끝난 프로미스 객체에 resolvereject를 호출하면 무시된다.

resolve와 reject 사용하기

resolve

const executor = (resolve, reject) => {
  setTimeout(() => {
    resolve('성공');
  }, 3000)
}

const promise = new Promise(executor);
promise.then((res) => {
  console.log(res);
})

프로미스 객체가 생성되면서 동시에 executor함수가 실행 -> executor함수에서는 매개변수로 받은 콜백함수 중 resolve함수를 사용해 '성공'이라는 값을 전달

이 때, 프로미스 객체의 state는 pending에서 fulfilled로 변경되고, result는 undefined에서 '성공'으로 변경된다.

reject

const executor = (resolve, reject) => {
  setTimeout(() => {
    reject('실패');
  }, 3000)
}

const promise = new Promise(executor);
promise.then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
})

reject함수가 실행되었기 때문에 프로미스의 객체의 state는 pending에서 rejected로, result는 undefined에서 '실패'로 변경된다.

여기서 작업이 성공했을 때 사용되는 메서드는 then이고, 작업이 실패했을 때의 결과값을 사용하기 위해서는 catch메서드를 사용해야 한다.


콜백지옥을 해결하는 Promise 객체

위에서 겪었던 콜백지옥 함수를 프로미스 객체를 사용해 다시 작성해보자.

const workA = () => {};

const workB = () => {};

const workC = () => {};
const workA = (value) => {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(value + 5);
    }, 5000)
  });
  return promise;
};
const workB = (value) => {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(value - 3);
    }, 3000);
  });
  return promise;
};

const workC = (value) => {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(value + 10);
    }, 10000);
  });
  return promise;
};
workA(10).then((resA) => {
  console.log(`1. ${resA}`);
  workB(resA).then((resB) => {
    console.log(`2. ${resB}`);
    workC(resB).then((resC) => {
      console.log(`3. ${resC}`);
    });
  });
});

작성해 본 결과, 코드가 많이 깔끔해지긴 했지만 여전히 콜백지옥의 코드와 많이 닮아있다.

then 메서드를 다른방식으로 사용하면 위 코드를 더 깔끔하게 작성할 수 있다.

프로미스 체이닝

이번에는 then메서드의 콜백함수 안에서 workBreturn해보자.

workA(10).then((resA) => {
  console.log(`1. ${resA}`);
  return workB(resA);
}).then((resB) => {
  console.log(`2. ${resB}`);
  return workC(resB);
}).then((resC) => {
  console.log(`3. ${resC}`);
});

이렇게 workB함수가 반환되게 하면, workB함수의 반환값인 프로미스 객체가 반환되는 것이기 때문에 다시 then을 사용할 수 있게 된다.

위와같이 계속해서 프로미스 객체를 반환하며 then 메서드를 연속으로 사용하는 것을 프로미스 체이닝 이라고 한다.

프로미스 체이닝을 사용하면 코드를 아래쪽으로 계속 작성할 수 있기 때문에 더 깔끔해지고, 직관적인 코드해석이 가능해진다.