goongoguma's blog

Promise

  • 자바스크립트에서 사용되는 비동기식 처리 모델은 요청을 병렬로 처리하여 다른 요청이 작업 중단 되지않는 장점이 있다.
  • 비동기 처리를 위해 콜백 패턴을 사용하면 처리 순서를 보장하기 위해 콜백함수가 중첩되어 복잡도가 높아지는 콜백 지옥이 발생한다는 단점이 된다.
step1(function (value1) {
  step2(value1, function (value2) {
    step3(value2, function (value3) {
      step4(value3, function (value4) {
        step5(value4, function (value5) {
          // value5를 사용하는 처리
        });
      });
    });
  });
});
  • 콜백 방식의 비동기 처리 문제점중 하나는 에러처리가 힘들다는것이다.
try {
  setTimeout(() => {
    throw new Error("Error!");
  }, 1000);
} catch (e) {
  console.log("에러를 캐치하지 못한다..");
  console.log(e);
}

비동기 처리 함수의 콜백 함수는 이벤트가 발생하면 태스크 큐로 이동한 후 호출 스택이 비어졌을 때, 호출 스택으로 이동되어 실행된다. setTimeout 함수는 비동기 함수이므로 콜백 함수가 실행될 때까지 기다리지 않고 즉시 종료되어 호출 스택에서 제거된다. 이후 tick 이벤트가 발생하면 setTimeout 함수의 콜백 함수는 태스크 큐로 이동한 후, 호출 스택이 비어졌을 때 호출 스택으로 이동되어 실행된다. 이때 setTimeout 함수는 이미 호출 스택에서 제거된 상태이다. 즉 setTimeout 함수의 콜백 함수를 호출한 것은 setTimeout 함수가 아니다. 따라서 setTimeout 함수의 콜백 함수 내에서 발생시킨 에러는 catch 블록에서 캐치되지 않고 프로세스가 종료된다.

  • Promise는 Promise 생성자 함수를 통해 인스턴스화 하며, Promise 생성자 함수는 비동기 작업을 수행할 콜백 함수를 인자로 전달받는다. 또한 해당 콜백 함수는 resolve와 reject 함수를 인자로 전달받는다.
// Promise 객체의 생성
const promise = new Promise((resolve, reject) => {
  // 비동기 작업을 수행한다.

  if (/* 비동기 작업 수행 성공 */) {
    resolve('result');
  }
  else { /* 비동기 작업 수행 실패 */
    reject('failure reason');
  }
});
  • Promise의 비동기 처리 상태 정보
    • pending → 비동기 처리가 아직 수행되지 않음
    • fulfilled → 비동기 처리가 성공적으로 수행된 상태
    • rejected → 비동기 처리가 실패한 상태
    • settled → 비동기 처리가 수행된 상태 (성공 or 실패)
  • Promise 생성자 함수가 인자로 전달받은 resolve와 reject 콜백 함수는 내부에서 비동기 처리 작업을 수행한다. Promise가 성공하면 resolve 함수를 실행하고 프로미스는 fulfilled 상태, 반대로 실패하면 reject 함수를 싱행하고 프로미스는 rejected 상태가 된다.
  • Promise로 구형된 비동기 함수는 Promise 객체를 반환하며 후속 처리 메소드인 then과 catch, finally 를 이용해 비동기 처리 결과 또는 에러를 전달받아 처리하며 상태에 따라 메소드를 체이닝 방식으로 호출한다.
  • 비동기 처리 시에 발생한 에러는 then 메서드의 두번째 콜백 함수로 처리할 수 있다.
const wrongUrl = "https://jsonplaceholder.typicode.com/XXX/1";

// 부적절한 URL이 지정되었기 때문에 에러가 발생한다.
promiseAjax(wrongUrl).then(
  (res) => console.log(res),
  (err) => console.error(err)
); // Error: 404
  • catch 메서드를 호출하면 내부적으로 then(undefined, onRejected)을 호출한다.
const wrongUrl = "https://jsonplaceholder.typicode.com/XXX/1";

// 부적절한 URL이 지정되었기 때문에 에러가 발생한다.
promiseAjax(wrongUrl)
  .then((res) => console.log(res))
  .then(undefined, (err) => console.error(err)); // Error: 404
  • 하지만, then 메서드의 두번째 콜백 함수는 첫번째 콜백 함수에서 발생한 에러를 캐치하지 못하고 가독성이 좋지 않기 때문에 추천하지 않는다.
promiseAjax("https://jsonplaceholder.typicode.com/todos/1").then(
  (res) => console.xxx(res),
  (err) => console.error(err)
);
  • 대신 catch를 사용하면 then 메서드를 호출한 이후에 비동기 처리에서 발생한 에러뿐만 아니라 then 메서드 내부에서 발생한 에러까지 모두 캐치할 수 있다.
promiseAjax("https://jsonplaceholder.typicode.com/todos/1")
  .then((res) => console.xxx(res))
  .catch((err) => console.error(err)); // TypeError: console.xxx is not a function
  • Promise는 주로 생성자 함수로 사용되지만 함수도 객체이므로 메서드를 가지고있다. Promise 객체는 4가지 정적 메서드를 제공한다.

    • Promise.resolve → 인자로 전달된 값을 resolve하는 Promise를 생성
    const resolvedPromise = Promise.resolve([1, 2, 3]);
    resolvedPromise.then(console.log); // [ 1, 2, 3 ]
    
    // 위의 예제는 아래와 같다
    const resolvedPromise = new Promise((resolve) => resolve([1, 2, 3]));
    resolvedPromise.then(console.log); // [ 1, 2, 3 ]
    
    • Promise.reject → 인자로 전달된 값을 reject하는 Promise를 생성
    const rejectedPromise = Promise.reject(new Error("Error!"));
    rejectedPromise.catch(console.log); // Error: Error!
    
    // 위의 예제는 아래와 같다
    const rejectedPromise = new Promise((resolve, reject) =>
      reject(new Error("Error!"))
    );
    rejectedPromise.catch(console.log); // Error: Error!
    
    • Promise.all → 프로미스가 담겨있는 배열과 같이 순회가 가능한 요소들을 인자로 받는다. 그리고 해당 요소들을 병렬로 처리한 뒤에 resolve하는 프로미스로 반환한다. 처리 순서가 보장되며 프로미스 처리가 하나라도 실패하면 에러를 reject하는 새로운 프로미스를 반환한다.
    • 전달받은 요소가 순회가능한 요소가 아닌 경우 Promise.resolve 메소드를 통해 프로미스로 감싼다.
    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(console.log) // [ 1, 2, 3 ]
      .catch(console.log);
    
    // 에러 발생시
    Promise.all([
      new Promise((resolve, reject) =>
        setTimeout(() => reject(new Error("Error 1!")), 3000)
      ),
      new Promise((resolve, reject) =>
        setTimeout(() => reject(new Error("Error 2!")), 2000)
      ),
      new Promise((resolve, reject) =>
        setTimeout(() => reject(new Error("Error 3!")), 1000)
      ),
    ])
      .then(console.log)
      .catch(console.log); // Error: Error 3!
    
    // 순회가능한 요소가 아닐때
    Promise.all([
      1, // => Promise.resolve(1)
      2, // => Promise.resolve(2)
      3, // => Promise.resolve(3)
    ])
      .then(console.log) // [1, 2, 3]
      .catch(console.log);
    
    • Promise.race → Promise.all과 같이 순회 가능한 요소들을 인자로 받지만 가장 먼저 처리된 프로미스가 resolve한 결과를 새로운 프로미스로 반환한다. 에러가 발생했을 경우 Promise.all과 동일하게 처리된다.
    Promise.race([
      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(console.log) // 3
      .catch(console.log);
    

출처: Promise