Dev./javascript

[JS] Promise의 이해

인쥭 2021. 6. 10. 11:07
반응형

1. Promise 코드 예시

promise[명사] 약속
  • 단어의 의미로 보아, javascript와 무엇인가 약속을 하는 것으로 짐작해볼 수 있다.
  • 우선 다음의 코드 블록을 보자.
function getMessage() {
  return "hello monkey";
}

console.log("start");
console.log(getMessage());
console.log("end");
  • 절차적으로 실행되는 javascript의 특성상 start, hello monkey, end가 순서대로 console에 출력될 것을 어렵지 않게 짐작할 수 있다.

절차적 실행

  • getMessage 함수가 Promise 인스턴스를 반환하도록 다음과 같이 수정하여 실행해보자.
function getMessage() {
  return new Promise((resolve) => {
    resolve("hello monkey");
    console.log("I'm in Promise!!!");
  });
}

console.log("start");
const youPromisedMe = getMessage();
youPromisedMe.then((message) => {
  console.log(message);
});
console.log("end");
  • Promise를 이해하지 못했더라도, 지금은 우선 단순한 console.log 작성 순서에 집중하고 실행 결과를 보자.

Promise를 활용한 실행

  • Promise 오브젝트 내부의 코드는 비동기적으로 실행되었다. 위 코드를 한줄씩 생각해보면 다음과 같다.
/* 
getMessage 함수를 실행하면 Promise 인스턴스를 반환한다.
반환되는 Promise 인스턴스는 항상 성공하며, 
resolve를 통해 "hello monkey" 값을 then의 핸들러 함수에 전달하게 될 것이다.
또한, Promise 인스턴스의 매개변수로 전달된 함수에서는 console.log도 실행하고 있다.
*/
function getMessage() {
  return new Promise((resolve) => {
    resolve("hello monkey");
    console.log("I'm in Promise!!!");
  });
}

// start를 콘솔에 출력한다.
console.log("start");

/* 
미리 선언된 getMessage 함수를 호출하므로,
youPromisedMe 변수에 Promise 인스턴스를 할당한다.
*/
const youPromisedMe = getMessage();

/* 
youPromisedMe라는 Promise가 성공했을 때, 
resolve에 의해 전달된 값을 message로 받아 콘솔에 출력한다.
*/
youPromisedMe.then((message) => {
  console.log(message);
});

// end를 콘솔에 출력한다.
console.log("end");
  • 이렇게만 보면 실행 순서는 start > I'm in Promise!!! > hello monkey > end가 되어야할 것 같은데, 실제로는 hello monkey가 마지막에 출력된다.
    • 즉, then()의 내용이 절차적으로 실행되지 않았다!
    • 이로 미루어 Promise는 어떤 조건이 만족되었을 때 then()의 내용을 실행할 수 있도록 약속하는 객체로 짐작해볼 수 있다.
  • 이 시점에서, 몇 가지의 Promise 특징을 짚어보자.

 

2. Promise의 작성

promise는 비동기 작업을 위한 객체이다.

  • Promise는 javascript에서 비동기 작업에 사용되는 객체이다.
  • 비동기적으로 실행될 작업을 Promise의 매개변수에 다음과 같은 형식으로 넣어준다.
new Promise( (resolve, reject) => {
  /*
  비동기 작업 성공시 resolve()를 호출하고,
  비동기 작업 실패시 reject()를 호출하도록 구현한다.
  */
});
  • Promise 인스턴스의 매개변수로 전달되는 함수의 매개변수는 반드시 resolve, reject일 필요는 없다. 짝만 맞춰주면 된다!
function getMessage(succeed) {
  return new Promise((code, monkey) => {
    if(succeed) {
      code("성공!");
    } else {
      monkey("실패...");
    }
  });
}

const weirdPromise = getMessage(true);
weirdPromise.then(
  (message) => { console.log(message) }
);

/* 실행 결과
성공!
*/
  • 그러나 시맨틱상 resolve, reject 등 의미가 명확히 전달되는 명칭을 사용하는 것이 권장된다.
    • resolve와 reject는 하술할 것.

 

3. Promise의 상태

Promise의 세 가지 상태

  • Promise는 세 개중 하나의 상태를 갖는다.
    • pending(대기): Promise가 생성되어 작업을 진행 중인 상태
    • fulfilled(이행): Promise가 작업을 성공적으로 완료한 상태
    • rejected(거절됨): Promise가 작업을 완료하였지만 실패한 상태
  • 완료 상태인 fulfilled와 rejected를 합쳐 settled라고도 한다고 함.
  • 이러한 상태 정보는 [[PromiseState]]에 명시된다.

Promise의 상태 확인

  • 위 내용은 다음과 같다.
    • prom1: 아무 작업을 수행하지 않는 Promise를 생성하였으므로, 상태는 pending
    • prom2: resolve가 즉시 실행되는 Promise이므로, 상태가 fulfilled로 변경됨
    • prom3: reject가 즉시 실행되는 Promise이므로, 상태가 rejected로 변경됨
  • 이렇듯, resolve와 reject는 각각 Promise의 상태를 변경하기 위해 사용하는 것을 알 수 있다.
  • 때문에 1.의 코드블록에서 hello monkey가 뒤늦게 실행되었다.
    • new Promise() 내부에 작성한 resolve는 이행 상태로 변경할 때 어떤 파라미터를 넘겨줄지 명시한 것이며,
      실제로 이행 상태인 Promise를 생성한 것이 아니기 때문이다.

 

4. Promise의 메소드

Promise와 관련된 메소드들

  • Promise와 관련된 메소드를 확인하기 위해 new Promise(()=>{});와 같이, 아무런 작업도 하지 않는 Promise를 객체에 할당하자.
  • obj.__proto__에는 Promise로부터 상속받은 메소드들이 설정된다.
    • 이 중, Promise의 작업 완료시 [[PromiseState]]에 따라 동작하는 then()과 catch()를 살펴보자.
  • then()은 다음과 같은 형식으로 사용할 수 있다.
    • Promise.then(함수1, 함수2)
  • 함수1은 Promise가 resolve되었을 때 resolve의 매개변수로 주어진 값을 받아 처리하는 성공 핸들러 함수이다.
  • (Optional) 함수2는 Promise가 reject되었을 때, reject의 매개변수로 주어진 값을 받아 처리하는 실패 핸들러 함수이다.
    • Optional한 이유는 catch()에서 하술
function getMessage(succeed) {
  return new Promise((resolve, reject) => {
    if(succeed) {
      resolve("성공!");
    } else {
      reject("실패...");
    }
  });
}

function successHandler(message) {
  console.log(`성공했을 경우 ${message}이 출력`);
}
function failHandler(message) {
  console.log(`실패했을 경우 ${message}가 출력`);
}

const succeedPromise = getMessage(true);
succeedPromise.then(successHandler, failHandler);

const failedPromise = getMessage(false);
failedPromise.then(successHandler, failHandler);

/* 실행 결과
성공했을 경우 성공!이 출력
실패했을 경우 실패...가 출력
*/
  • catch()Promise.catch(함수) 형식으로 사용한다.
    • then()의 함수2가 optional한 이유는 catch()의 존재가 있기 때문이다.
    • Promise.then(함수1, 함수2)를 Promise.then(함수1).catch(함수2) 형태로 사용할 수 있다.
function getMessage(succeed) {
  return new Promise((resolve, reject) => {
    if(succeed) {
      resolve("성공!");
    } else {
      reject("실패...");
    }
  });
}

function successHandler(message) {
  console.log(`성공했을 경우 ${message}이 출력`);
}
function failHandler(message) {
  console.log(`실패했을 경우 ${message}가 출력`);
}

const succeedPromise = getMessage(true);
succeedPromise
  .then(successHandler)
  .catch(failHandler);

const failedPromise = getMessage(false);
failedPromise
  .then(successHandler)
  .catch(failHandler);
  
/* 실행 결과
성공했을 경우 성공!이 출력
실패했을 경우 실패...가 출력
*/
  • Promise.then(함수1, 함수2)는 성공 핸들러 함수인 함수1의 에러를 함수2에서 처리할 수 없다. 따라서 별도의 로직이 필요하다.
  • Promise.then(함수1).catch(함수2)는 성공 핸들러 함수인 함수1의 에러를 함수2에서 처리할 수 있다!
  • 이는 then(), catch()의 반환값이 새로운 Promise 객체이기 때문이다.
function getMessage(succeed) {
  return new Promise((resolve, reject) => {
    if(succeed) {
      resolve("성공!");
    } else {
      reject("실패...");
    }
  });
}
function successHandler(message) {
  console.log(`성공했을 경우 ${message}이 출력`);
  throw new Error();
}
function failHandler(message) {
  console.log(`실패했을 경우 ${message}가 출력`);
}

const catchPromise = getMessage(true);
catchPromise
  .then(successHandler)
  .catch(failHandler);

/* 실행 결과
성공했을 경우 성공!이 출력
실패했을 경우 Error가 출력
// successHandler에서 실행된 throw new Error()를 처리하였음!
*/

// const onlyThenPromise = getMessage(true);
// onlyThenPromise.then(successHandler, failHandler);
/* 실행 결과
성공했을 경우 성공!이 출력
// successHandler에서 실행된 throw new Error()를 처리하지 못했음!
*/
  • then()과 catch()가 반환하는 것은 새로운 Promise 객체이므로 method chaining이 가능하다.
function getMessage(succeed) {
  return new Promise((resolve, reject) => {
    if(succeed) {
      resolve("성공!");
    } else {
      reject("실패...");
    }
  });
}
function successHandler(message) {
  console.log(`성공했을 경우 ${message}이 출력`);
  return "code monkey";
}
function failHandler(message) {
  console.log(`실패했을 경우 ${message}가 출력`);
}

const monkeyPromise = getMessage(true);
monkeyPromise
  .then(successHandler)
  .then(successHandler)
  .catch(failHandler);
  
/* 실행 결과
성공했을 경우 성공!이 출력
성공했을 경우 code monkey이 출력
*/
  • successHandler가 return하는 문자열은 then 메소드가 받아 새로운 Promise 인스턴스를 생성하여 다음 메소드로 넘겨주고 있다.