Programming Language/JavaScript

[JavaScript] Promise 객체 기본 개념

  • -

프로미스 객체란?

프로미스(Promise) 객체는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체입니다.

즉 비동기 함수의 실행 상태에 따라 각각의 함수를 호출하여 작업의 결과를 나타내는 것입니다.

 

 

 

예시

비동기적으로 이미지파일을 생성하는 createImageFile() 이라는 함수가 있을 때, 이 함수의 파라미터로는 (1)이미지 파일의 이름, (2)성공 시 호출할 함수, (3)실패 시 호출할 함수가 있습니다.

function createImageFile(fileName, successCallBack, failureCallBack){
    // 이미지 파일 생성중
    if(success) successCallBack(fileName);
    else failureCallBack(fileName);
}

 

 

 

프로미스가 나타낼 수 있는 상태는 세 가지가 있습니다.

  1. 대기(pending) : 이행하지도, 거부하지도 않은 초기 상태
  2. 이행(fulfilled) : 작업이 완료된 상태(성공)
  3. 거부(rejected) : 작업이 거부된 상태(실패)

대기 상태에서 작업을 성공적으로 완료하는 것을 해결(resolve)라고 하고, 대기 상태에서 작업이 오류 발생 등 모종의 이유로 실패하는 것을 거부(reject)라고 합니다.

 

 

 

프로미스 객체 사용해보기

프로미스 객체는 함수를 콜백을 전달하는 대신에 콜백을 첨부하는 방식입니다.

위에서 살펴본 프로미스 객체의 자세한 사용 방법을 알아보겠습니다.

 

 

 

객체 생성

let myPromise1 = new Promise((resolve, reject) => {
    // 비동기 작업
});

프로미스 객체의 생성자를 사용했습니다.

프로미스의 생성자 매개변수에는 콜백 함수가 들어가고, 그 콜백함수는 성공과 실패를 각각 나타내는 두 개의 객체를 매개변수로 받습니다.

resolve는 비동기 처리가 성공했을 때 호출하는 함수, reject는 비동기 처리가 실패했을 때 호출하는 함수입니다.

 

 

 

객체 처리 (then, catch, finally)

then

then은 프로미스에서 가장 중요한 메서드입니다.

사용 방법은 아래와 같습니다.

myPromise.then(
    function(){},    // 성공 시 싫행하는 함수
    function(){}    // 실패 시 실행하는 함수
);

.then()의 첫 번째 인수는 프로미스의 비동기 처리가 성공했을 때 실행하는 함수이고,

두 번째 함수는 프로미스의 비동기 처리가 실패했을 때 실행하는 함수입니다.

 

 

 

catch

만약 프로미스의 비동기 처리가 실패한 경우만 다루고 싶다면 .catch()를 사용하면 됩니다.

myPromise.catch(
    function(){} // 실패시 실행할 함수
);

.catch()를 사용하는 것은 .then()의 첫 번째 인수에 null을 입력하는 것과 같습니다.

*프로미스는 성공 또는 실패만 합니다.

하나의 비동기 처리에 대해 프로미스는 성공 또는 실패만 할 수 있습니다.

따라 resolve를 실행하면 reject를 호출할 수 없고, reject를 실행하면 resolve를 실행할 수 없습니다.

 

 

finally

그럼 성공과 실패와 상관 없이 무조건 실행되게 하는 함수는 어떻게 처리해야 할까요?

finally를 사용하여 처리할 수 있습니다.

promise
    .then(value => { 
        // 성공
        console.log(`success data : ${value}`); 
    })
    .catch(() => { 
        // 실패
         console.error('failure'); 
    })
    .finally(() => { 
        console.log('end');
    })




함수 등록

위에서 본 방법들은 모두 체에 바로 함수를 부착하는 방식입니다.

이번에는 프로미스 객체를 반환하는 함수를 만들어 함수의 return 값을 사용해보겠습니다.

function getPromise(){
    return new Promise((resolve, reject) => {
        // 작업
        if(isResolved) resolve();
        else reject();
    });
}

getPromise()
    .then(() => { console.log("작업 성공") })
    .catch(() => { console.log("작업 실패") });


함수 등록 방식을 이용하는 이유는 다음과 같습니다.

  • 반복되는 비동기 작업을 효율적으로 처리할 수 있습니다.
  • 코드 구조가 명확해져 비동기 작업의 정의와 사용을 분리하여 가독성을 높일 수 있습니다.
  • 인자를 전달하여 동적으로 비동기 작업을 수행할 수 있습니다.

위 함수는 프로미스 팩토리함수라고도 합니다. 

 

 

Promise Chaining

프로미스의 가장 뛰어난 장점 중 하나인 Chaining에 대해 알아보겠습니다.

promise chaining은 이전 단계의 비동기 작업이 성공하고 나서의 결과값을 가지고 다음 비동기 작업을 수행해야 할 때 유용하게 쓰입니다.

 

체이닝을 사용하지 않았을 때

// 함수 정의
const getDataCallback = (num, callback) => {
    setTimeout(()=>{
        if(typeof num === 'number') 
            callback(undefined, num * 2);
        else 
            callback('숫자가 아닙니다.', num);
    },1000);
};

// 함수 호출
getDataCallback(2, (errMsg, data) => {
    if(errMsg !== undefined){
        console.log(errMsg)
    }
    else{
        getDataCallback(data, (errMsg, data) => {
            if(errMsg !== undefined) {
                console.log(errMsg);
            }else{
                getDataCallback(data, (errMsg, data) => {
                    if(errMsg !== undefined) {
                        console.log(errMsg);
                    }else{
                        console.log(data);
                    }
                });
            }
        });
    }
});

프로미스를 사용하지 않고, 비동기 작업을 연속적으로 수행하게 되면

콜백 지옥이 생겨납니다.

 

 

 

체이닝을 사용할 때

const getDataPromise = num => new Promise((resolve, reject) =>{
    setTimeout(() => {
        typeof num === 'number' ? resolve(num * 2) : reject('숫자가 아닙니다.');
    }, 1000);
});

getDataPromise(2)
    .then(data => getDataPromise(data))
    .then(data => getDataPromise(data))
    .then(data => console.log(data))    // 16을 출력
    .catch(errMsg => console.log(errMsg));

하지만 프로미스를 이용하면 코드가 더욱 간결해 보입니다.

이 때 주의해야 할 사항은 반환값이 반드시 있어야 한다는 것입니다.

반환값이 없다면 당연히 이전의 콜백 함수가 이전의 결과를 받지 못합니다.

 

 

 

 

promise chaining을 사용하는 이유

  • 코드를 더 효율적으로 짜기 위해 사용된다
  • 비동기 코드를 간단하게 짤 수 있다
  • 여러개의 프로미스 체인에서 하나라도 reject되면 바로 catch로 내려가서 error를 처리한다

 

 

 

 

* 위 글은 다음 문서를 참고하여 작성되었습니다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise

https://ko.javascript.info/promise-basics

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.