프로미스(Promise)란? 콜백함수와의 차이점

주형(Jureamer)·2022년 3월 14일
2
post-thumbnail

프로미스란?

"A promise is an object that may produce a single value some time in the future"

프로미스는 자바스크립트 비동기 처리에 사용되는 객체이다.
비동기 처리란 '특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성'이다.

**프로미스의 핵심은
즉 비동기 처리 방식에서 아래의 2가지를 이유로 들 수 있다.

  • return value를 이용할 수 있다는 점
  • error handling이 동기식 코드와 유사하게 쓰일 수 있다는 점

프로미스의 3가지 상태

프로미스를 사용할 때 알아야 하는 가장 기본적인 개념이 바로 프로미스의 상태(states)다. 여기서 말하는 상태란 프로미스의 처리 과정을 의미합니다. new Promise()로 프로미스를 생성하고 종료될 때까지 3가지 상태를 갖는다.

Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

Pending(대기)

먼저 아래와 같이 new Promise() 메서드를 호출하면 대기(Pending) 상태가 된다.

new Promise();

new Promise() 메서드를 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject가 된다.

new Promise(function(resolve, reject) {
  // ...
});

Fulfilled(이행)

여기서 콜백 함수의 인자 resolve를 아래와 같이 실행하면 이행(Fulfilled) 상태가 된다.

new Promise(function(resolve, reject) {
  resolve();
});

그리고 이행 상태가 되면 아래와 같이 then()을 이용하여 처리 결과 값을 받을 수 있다.

function getData() {
  return new Promise(function(resolve, reject) {
    var data = 100;
    resolve(data);
  });
}
// resolve()의 결과 값 data를 resolvedData로 받음
getData().then(function(resolvedData) {
  console.log(resolvedData); // 100
});

Rejected(실패)

new Promise()로 프로미스 객체를 생성하면 콜백 함수 인자로 resolve와 reject를 사용할 수 있다고 했습니다. 여기서 reject를 아래와 같이 호출하면 실패(Rejected) 상태가 됩니다.

new Promise(function(resolve, reject) {
  reject();
});

그리고, 실패 상태가 되면 실패한 이유(실패 처리의 결과 값)를 catch()로 받을 수 있다.

function getData() {
  return new Promise(function(resolve, reject) {
    reject(new Error("Request is failed"));
  });
}

// reject()의 결과 값 Error를 err에 받음
getData().then().catch(function(err) {
  console.log(err); // Error: Request is failed
});

프로미스의 처리흐름

프로미스 예제

function getData() {
  return new Promise(function(resolve, reject) {
    $.get('url 주소/products/1', function(response) {
      if (response) {
        resolve(response);
      }
      reject(new Error("Request is failed"));
    });
  });
}

// 위 $.get() 호출 결과에 따라 'response' 또는 'Error' 출력
getData().then(function(data) {
  console.log(data); // response 값 출력
}).catch(function(err) {
  console.error(err); // Error 출력
});

프로미스 체이닝

then메소드와 catch메소드의 반환 값(return)은
또 다른 프로미스 객체를 반환하기 때문에, 서로 Chaining이 가능하다.

const successPromise = new Promise((resolve, reject) => {
  setTimeout(function () {
    resolve("Success");
  }, 3000);
});

const anotherPromise = (value) => {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(`${value} not`);
    }, 1000);
  });
};

successPromise
  .then((value) => `${value} is`) //  `${value} is`를 결과 값으로 가진 Promise 객체 생성
  .then((secondValue) => anotherPromise(secondValue)) // 다른 프로미스가 처리될 때까지 기다리다가 처리가 완료되면 그 결과를 받음.
  .then((thirdValue) => console.log(thirdValue + " impossible"))
  .catch((error) => {
    errorHandling(error);
    return "again?"; // catch 메소드 이후에도 체이닝 가능.
  })
  .then((lastValue) => console.log(lastValue));

// 약 4초 후에 "Success is not impossible"을 출력 

finally

finally메소드는 Promise의 성공과 실패에 관계없이 처리만 되면 실행되는 함수이다.
따라서 finally에선 프라미스가 성공되었는지, 실패되었는지 알 수 없다.

const successPromise = new Promise((resolve, reject) => {
  setTimeout(function () {
    resolve("Success");
  }, 3000);
});

successPromise
  .then((value) => `${value} is`)
  .then((secondValue) => {
    throw new Error("Error!!");
  }) // 에러 발생
  .then((thirdValue) => console.log("possible"))
  .catch((error) => {
    console.log(error);
  })
  .finally(() => console.log("chain end"));
// 위 Promise상태가 어떻든 간에 Promise 객체가 반환되었기 때문에 finally 메소드가 무조건적으로 실행 됨.

Promise.all

Promise.all메소드는 배열과 같이 순회 가능한 객체(주로 거의 배열이라고 한다)를 인자로 받는다.
해당 배열 안의 프로미스가 모두 이행되면(배열 요소가 반드시 프로미스일 필요는 없다),
각각의 프로미스 결과 값을 담은 배열을
이행 결과 값으로 새로운 프로미스 객체를 반환한다.

const one = new Promise((resolve, reject) => {
  setTimeout(() => resolve("one"), 1000);
});
const two = new Promise((resolve, reject) => {
  setTimeout(() => resolve("two"), 2000);
});
const three = new Promise((resolve, reject) => {
  setTimeout(() => resolve("three"), 3000);
});

Promise.all([one, two, three]).then((val) => console.log(val));
/* 배열 안 모든 프로미스가 이행된 후(약 3초 이후) 각 이행 결과값을 담은 배열을
   결과값으로 갖는 프로미스 객체가 만들어져
   콘솔에는 ["one", "two", "three"]가 출력됨.*/

Promise.all(["Hi", 123, three]).then((val) => console.log(val));
/* 배열 안 요소가 반드시 프로미스가 아닌 경우에도 가능함.
  하지만 이 경우에도 요소 안애 프로미스가 있다면 프로미스가 이행된 이후에 프로미스 객체가 생성됨.*/

하지만 이 때 배열 요소 중 하나의 프로미스라가 거부되는 즉시,
다른 프로미스 이행 여부와 관계없이 해당 거부 사유를 결과 값으로 반환한다.

const one = new Promise((resolve, reject) => {
  setTimeout(() => resolve("one"), 1000);
});
const two = new Promise((resolve, reject) => {
  setTimeout(() => resolve("two"), 2000);
});
const three = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Error!!")), 3000);
});

Promise.all([one, two, three])
  .then((val) => console.log(val))
  .catch((err) => console.log(err));
  // 다른 프로미스 이행 여부와 관계없이 catch 메소드가 호출
반환하는 프로미스의 이행 값은 매개변수로 주어진 프로미스의 순서와 일치하며,
완료 순서에 영향을 받지 않는다.

const one = new Promise((resolve, reject) => {
  setTimeout(() => resolve("one"), 3000);
});
const two = new Promise((resolve, reject) => {
  setTimeout(() => resolve("two"), 2000);
});
const three = new Promise((resolve, reject) => {
  setTimeout(() => resolve("three"), 1000);
});

Promise.all([one, two, three]).then((val) => console.log(val));
// ["one", "two", "three"] 출력
// 배열의 첫번째 요소가 가장 마지막으로 이행 값을 반환했지만, 전달된 순서를 유지함.

위의 예시처럼 여러가지 비동기 작업을 병렬적으로 실행하는 과정에서
비동기 작업이 시작된 순서를 유지해야 되는 경우라면 Promise.all을 활용하면 된다.

콜백함수와의 차이점

콜백함수는 비동기 로직의 결과값을 처리하기 위해서 callback 안에서만 처리를 해야하고, 콜백 밖에서는 비동기에서 온 값을 알 수가 없다.
=> 프로미스를 사용하면 비동기에서 온 값이 프로미스 객체에 저장되기 때문에 코드 작성이 용이해진다.

Reference

profile
작게라도 꾸준히 성장하는게 목표입니다.

0개의 댓글