Post

프로미스(Promise)에 대해 알아보기

Promise

비동기 작업을 처리하기 위한 특수한 객체인 Promise는 기존의 콜백 함수에 대한 단점인 가독성, 에러 처리, 비동기 작업의 순차적 실행 등의 다양한 측면에서 우위를 가지고 있습니다.

Callback

Promise에 대한 설명에 들어가기 전에 앞서 이야기한 기존 콜백 함수에 대한 단점에 대해서 이야기 해보겠습니다.

Callback hell

비동기 처리에서 여러개의 비동기 작업을 처리하기 위해서 아래와 같이 콜백 함수를 중첩해서 사용하면 마치 지옥과도 같은 콜백 헬(callback hell)이 되어버립니다.

1
2
3
4
5
6
7
8
9
10
11
asyncFunctionA(function (valueA) {
  asyncFunctionB(function (valueB) {
    asyncFunctionC(function (valueC) {
        asyncFunctionD(function (valueD) {
          asyncFunctionE(function (valueE) {
              // ...
          });
        });
    });
  });
});

마치 피라미드를 뒤집어 놓은 형태처럼 변하며 이렇게 되면 코드의 가독성이 떨어지고 유지보수가 힘들어질 수 있습니다.

에러 처리의 어려움

일반적인 경우에는 try-catch문 등을 통하여 에러를 감지할 수 있지만 비동기 콜백의 경우 에러를 감지하지 못할 수 도 있습니다.

1
2
3
4
5
6
7
8
9
try {
    setTimeout(function() {
        throw new Error("Error");
    }, 1000)
    console.log('try');
} catch (e) {
    console.log(e);
    console.log('catch');
}

위의 코드를 예시로 보면 try 내부에서 1초후에 강제적으로 에러를 발생하게 끔 했는데 이것은 catch에서 감지되지 않습니다.

이유로는 비동기적으로 발생한 에러를 감지하지 못하기 때문입니다.

setTimeout 함수는 비동기적으로 동작하며, 지정된 시간(1000ms)이 지나면 콜백 함수가 호출됩니다. 따라서 setTimeout 함수가 호출되고 콜백 함수가 예약된 후, 바로 다음 줄로 넘어가는 것 입니다. 콜백 함수는 독립적으로 실행되므로, try-catch 문 바깥에서 발생한 에러는 catch 블록으로 잡히지 않습니다.

이렇게 되어 콜백 함수에서 발생한 에러는 이미 바깥으로 빠져나간 상태로 로그와 외부에서의 에러문만을 호출하는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
    setTimeout(function() {
        try {
            throw new Error("Error");
        } catch (e) {
            console.log(e);
            console.log('catch');
        }
    }, 1000);
    console.log('try');
} catch (e) {
    console.log(e);
}

만약에 setTimeout 함수 내부에서 오류를 감지하고 싶다면 위의 코드처럼 그 내부에서 try-catch문을 또 걸어야 할 것 입니다.

Promise 사용법

일반적으로 Promise 객체를 생성할 때는 new Promise() 구문을 사용하며, 콜백 함수를 인자로 받습니다. Promise는 resolvereject라는 두 개의 함수를 인자로 받으며 Promise 객체의 상태를 결정하는 역할을 합니다.

1
2
3
4
5
6
7
8
9
const promise = new Promise(function (resolve, reject) {
    if (/* 조건문 */) {
      /* 비동기 작업의 성공 */
      resolve('success');
    } else {
      /* 비동기 작업의 실패 */
      reject('error');   
    }
});

Promise의 상태

Promise는 다음과 같은 3가지의 상태를 가질 수 있습니다.

  • 대기(pending): 비동기 작업이 완료되지 않은 초기 상태
  • 이행(fulfilled): 비동기 작업이 성공적으로 완료된 상태로 결과 값이 존재
  • 거부(rejected): 비동기 작업이 실패한 상태로 실패 이유를 나타내는 에러가 존재

pending 상태

1
2
3
4
5
6
/* 아직 비동기 작업이 완료되지 않은 상태 */
const promise = new Promise((resolve, reject) => {
  // ...
});

console.log(promise); // Promise { <pending> }

fulfilled 상태

이행된 상태로 예시 코드처럼 resolve 함수를 호출하여 Promise 객체를 fulfilled 상태로 변경한 후, then() 메서드를 통해 등록된 콜백 함수가 실행되어 결과 값을 받을 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
/* 비동기 작업이 성공적으로 완료된 상태 */
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Success');
    }, 1000);
});

promise.then((result) => {
  console.log(result); // Success
});

console.log(promise); // Promise { <pending> }

rejected 상태

거부된 상태로 예시 코드처럼 reject 함수를 호출하여 Promise 객체를 rejected 상태로 변경한 후, catch() 메서드를 통해 등록된 콜백 함수가 실행되어 에러 메시지를 출력합니다.

1
2
3
4
5
6
7
8
9
10
11
12
/* 비동기 작업이 실패한 상태 */
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error('Failed'));
  }, 1000);
});

promise.catch((error) => {
  console.log(error.message); // Failed
});

console.log(promise); // Promise { <pending> }


아래는 프로미스의 흐름도로 Promise 객체의 비동기 작업에 대한 성공과 실패의 경우에 대한 일련의 과정을 보여줍니다.

promises

사진 출처: MDN Web Docs

인스턴스 메서드

Promise 객체에는 then, catch, finally 메서드가 존재하며, 이 3개의 메서드는 각각 다음과 같은 역할을 수행합니다.

then

then은 Promise 객체가 성공적으로 완료되었을 때 실행할 콜백 함수를 등록하는 메서드로 2개의 인자를 가지고 있습니다.

작업이 성공적으로 완료되었을 때와 선택적으로 작업이 실패했을 때 실행할 콜백 함수입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 에러가 발생 했을 때의 경우 */
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(new Error('Failed'));
    }, 1000);
});

promise.then(
    function (result) {
        console.log('Success');
        console.log(result);
    }, 
    function (err) {
        console.log('Failed'); // Failed 로그 출력과 에러 메시지를 출력
        console.log(err);
    });

추가적으로 then는 연속적으로 체인 형태로 호출할 수 있으며, 각각의 then 메서드는 새로운 Promise 객체를 반환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 체인 형태로 호출하는 경우 */
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("Successfully");
    }, 1000);
});

promise.then(function (result) {
    console.log(result); // Successfully

    return result;
}).then(function (result) {
    console.log(result); // Successfully
});

catch

catch는 Promise 객체에서 오류가 발생했을 때 실행할 콜백 함수를 등록하는 메서드로 단일 인자만 존재합니다. then의 실패 콜백을 catch로 대체할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
/* promise의 then의 onRejected를 catch로 대체한 예시 */
const promise = new Promise(function (resolve, reject) {
   setTimeout(function () {
       reject(new Error ("Error"));
   }, 1000); 
});

promise.then(function (result) {
  console.log(result);
}).catch(function (error) {
  console.log(error);
});

finally

finally는Promise 객체의 성공 또는 실패와 관계없이 마지막으로 실행하기 위한 콜백 함수를 등록하는 메서드 입니다. thencatch의 앞에 위치 할 수도 있지만 실행은 마지막에 진행됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const promise = new Promise(function (resolve, reject) {
    const result = true;
    
    if (result) {
        resolve(result);
    } else {
        reject(new Error('Something went wrong'));
    }
});

promise
    .then(function(result) {
        // 성공 시 실행되는 콜백 함수
        console.log('Success:', result);
    })
    .catch(function(error) {
        // 실패 시 실행되는 콜백 함수
        console.error('Error:', error);
    })
    .finally(function() {
        // 항상 실행되는 콜백 함수
        console.log('Finally');
    });


Reference

JAVASCRIPT.INFO: 프라미스

MDN Web Docs: Promise

poiemaweb: Promise

CAPTAIN PANGYO:자바스크립트 Promise 쉽게 이해하기

모던 자바스크립트 Deep Dive (자바스크립트의 기본 개념과 동작 원리)

This post is licensed under CC BY 4.0 by the author.