(JS) 벨로퍼트의 모던 자바스크립트 : Promise

호두파파·2021년 3월 2일
0

Promise

프로미스는 비동기 작업을 조금 더 편하게 처리할 수 있도록 ES6에 도입된 기능이다. 이전에는 비동기 작업을 처리할 때에는 콜백 함수로 처리를 해야 했었는데, 콜백 함수로 처리를 하게 된다면 비동기 작업이 많아질 경우 코드가 쉽게 난잡해질 수 있다.

한번 숫자 n을 파라미터로 받아와서 다섯번에 걸쳐 1초마다 1씩 더해서 출력하는 작업을 setTimeout으로 구현하면 무수히 많은 콜백의 연속을 볼 수 있다.(콜백지옥)

function increaseAndPrint(n, callback) {
  setTimeout(() => {
    const increased = n + 1;
    console.log(increased);
    if (callback) {
      callback(increased);
    }
  }, 1000);
}

increaseAndPrint(0, n => {
  increaseAndPrint(n, n => {
    increaseAndPrint(n, n => {
      increaseAndPrint(n, n => {
        increaseAndPrint(n, n => {
          console.log('끝!');
        });
      });
    });
  });
});

이런 식으로 읽기 어려운 코드를 Callback Hell(콜백지옥)이라고 부른다.
비동기적으로 처리해야 하는 일이 많아질수록, 코드의 깊이가 깊어지는 현상을 방지할 수 있다.

Promise 만들기

const MyPromise = new Promise((resolve, reject) => {
  // 구현 ...
})

Promise는 성공할 수도 있고, 실패할 수도 있다. 성공할 때에는 resolve를 호출해주면되고, 실패할 때는 reject를 호출해주면 된다.

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

myPromise.then(n => {
  console.log(n);
});

resolve를 호출할 때 특정 값을 파라미터로 넣어주면, 이 값을 작업이 끝나고 나서 사용할 수 있다. 작업이 끝나고 다시 또 다른 작업을 해야할 때에는 Promise 뒤에.then(...)을 붙여서 사용하면 된다.

1초 뒤에 실패하게끔 세팅해보기

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error());
  });
  
  myPromise
  	.then(n => {
      console.log(n);
  	})
  	.catch(error => {
      console.log(error);
  });

실패하는 상황에서는 reject를 사용하고, .catch를 통해 실패했을 시 수행할 작업을 설정할 수 있다.

function increaseAndPrint(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const value = n + 1;
      if (value === 5) {
        const error = new Error();
        error.name = 'ValueIsFiveError';
        reject(error);
        return;
      }
      console.log(value);
      resolve(value);
    }, 1000);
  });
}

increaseAndPrint(0).then((n) => {
  console.log('result: ', n);
})

Promise의 속성 중에는, 만약 then 내부에 넣은 함수에서 또 Promise를 리턴하게 된다면, 연달아서 사용할 수 있다.

function increaseAndPrint(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const value = n + 1;
      if (value === 5) {
        const error = new Error();
        error.name = 'ValueIsFiveError';
        reject(error);
        return;
      }
      console.log(value);
      resolve(value);
    }, 1000);
  });
}

increaseAndPrint(0)
  .then(n => {
    return increaseAndPrint(n);
  })
  .then(n => {
    return increaseAndPrint(n);
  })
  .then(n => {
    return increaseAndPrint(n);
  })
  .then(n => {
    return increaseAndPrint(n);
  })
  .then(n => {
    return increaseAndPrint(n);
  })
  .catch(e => P
    console.error(e);
  });


위 코드는 다음과같이 정리할 수 있다.

function increaseAndPrint(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const value = n + 1;
      if (value === 5) {
        const error = new Error();
        reject(error);
        return;
      }
      console.log(value);
      resolve(value);
    }, 1000);
  });
}

increaseAndPrint(0)
  .then(increaseAndPrint)
  .then(increaseAndPrint)
  .then(increaseAndPrint)
  .then(increaseAndPrint)
  .then(increaseAndPrint)
  .catch(e => {
    console.error(e);
  });

Promise를 사용하면, 비동기 작업의 갯수가 많아져도 코드의 깊이가 깊어지지 않게 된다.

하지만 Promise도 불편한 점이 있긴 마찬가지이다. 에러를 잡을 때 몇번째에서 발생했는지 알아내기도 어렵고 특정 조건에 따라 분기를 나누는 작업도 어렵고, 특정 값을 공유해가면서 작업을 처리하기도 까다롭다. async/await를 사용하면, 이러한 문제점을 해결할 수 있다.

profile
안녕하세요 주니어 프론트엔드 개발자 양윤성입니다.

0개의 댓글