(JS) 동기와 비동기 (2 / 3) : Promise

호두파파·2021년 2월 14일
1

JavaScript

목록 보기
17/25
post-thumbnail

Promise


프로미스 처리 흐름 (출처 MDN)

  • 프러미스는 기본적으로 아래와 같은 형태의 객체이다.
const promise = new Promise()
  • 프러미스 생성자 함수는 함수를 인자로 받는다.
const promise = new Promise(function () {
  // something..
});
  • 프러미스 생성자 함수에 인자로 들어갈 함수는 일반적으로 resolve, reject라고 부르는 2개의 매개 변수를 사용할 수 있다. resolvereject는 함수이다.
const promise = new Promise(function (resolve, reject) {
  // do something async here
});

프러미스 생성자 함수에 인자로 들어간 함수 내부에서 우리는 비동기 작업을 하고, 비동기 작업이 성공할 경우 resolve를 실행해야 하고, 실패할 경우 reject를 실행해야 한다.

const promise = new Promise(function (resolve, reject) {
  // do something async here..
  fs.readFile(path, "utf-8", (err, data) => {
    if (err) {
      reject(err);
    } else {
      resolve(data);
    }
  });
});

위와 같이 생성한 프로미스 객체는 미래에 맞이할 성공 혹은 실패에 대한 결과값을 나타낸다. 미래에 결과를 돌려주겠다는 약속이다. 모든 프러미스 객체는 다음 세 가지 상태 중 하나의 상태를 가지며, 한번이라도 성공하거나 실패한 프러미스는 초기 상태로 돌아갈 수 없다.

  • Pending : 아직 결과가 정해지지 않은 상태(초기의 상태)
  • Fullfilled : 연산이 성공한 상태
  • Rejected : 연산 실패한 상태

프러미스 인스턴스는 .then, .catch등의 메소드를 사용할 수 있다.
위 메소드는 체이닝이 가능하다.

const promise = new Promise(function (resolve, reject) {
  // do something async here..
  fs.readFile(path, "utf-8", (err, data) => {
    if (err) {
      reject(err);
    } else {
      resolve(data);
    }
  });
});

promise.then(function (data) {
  console.log("Promise success!", data);
});

promise.catch(function (err) {
  console.log("Promise Failed!", err);
});

resolve(결과)의 결과가 then의 result로 가고, reject(err)의 err이 catch의 err로 간다.

그리고 여러 단계를 아래와 같이 순차적으로 연결할 수도 있다.
(thend과 catch는 Promise 객체의 메소드이다. 만약 then이 또 다른 값을 반환한다면, then을 다음에 한 번 더 연결해서 처리할 수 있다.)

promise.then(function done (data) {
  console.log("Promise success!", data);
  return 1;
}).then(function handleOne (one) {
  console.log("I am one.");
  return one + 1;
}).then(function handleTwo (two) {
  console.log("I am two.");
}).catch(function handleError (err) {
  console.log("Promise Failed!", err);
});

이 메소드는 모든 프로미스가 성공하면 then, 하나라도 실패하면 catch로 연결된다.


promise 끝에 catch 붙여야 하는 이유

개개인의 코딩 스타일에 따라서 then()의 두 번째 인자로 처리할 수도 있고, catch()로 처리할 수도 있겠지만, 가급적 catch()로 에러를 처리하는 것이 더 효율적이다.

function getData() {
  return new Promise(function(resolve, reject) {
    resolve('hi');
  });
}

getData().then(function(result) {
  console.log(result);
  throw new Error("Error in then()"); // Uncaught (in promise) Error: Error in then()
}, function(err) {
  console.log('then error : ', err);
});

getDate()함수의 프러미스에서 resolve()메서드를 호출해 정상적으로 로직을 처리했지만, then() 첫 번째 콜백 함수 내부에서 오류가 나는 경우 오류를 제대로 잡아내지 못한다.

하지만 똑같은 오류를 catch()로 처리하면 다른 결과가 나온다.

// catch()로 오류를 감지하는 코드
function getData() {
  return new Promise(function(resolve, reject) {
    resolve('hi');
  });
}

getData().then(function(result) {
  console.log(result); // hi
  throw new Error("Error in then()");
}).catch(function(err) {
  console.log('then error : ', err); // then error :  Error: Error in then()
});

위 처리 결과는 다음과 같다.


정리

프러미스를 이용했을때 기존 콜백 지옥과는 몇 가지 큰 차이를 갖는다.

우선
1. 동기 흐름의 코드에서 함수 연산에 대한 결과로서 반환값을 지정할 수 있고,
2. 그 결과를 받아 추가적인 연산을 진행할 수 있다.

콜백 함수를 이용한 상황에서는 비동기 함수의 반환값을 받을 수 없기 때문에, 동기 함수처럼 반환값ㅇ르 지정해줄 수 없었다.

하지만 프러미스를 이용할 결우, (성공이나 실패에 대한 결과를 알려주는) 약속을 담은 객체가 손에 쥐어지기 때문에 프러미스 객체를 이용해 반환하거나 자유롭게 다른 모듈로 넘겨줄 수 있다. 비동기 상황에서도 동기적 코드의 흐름과 조금 더 유사하게 제어할 수 있도록 개선된 것이다.

하지만, 이런 Promise 조차도 Promise hell이 발생할 수 있어, async/await 문법이 등장하게 되었다.


3편 보기 : Async / Await

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

0개의 댓글