웹개발

자바스크립트 Promise

Sh4869 2021. 1. 14. 22:57

Promise

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

 

"instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future"

 

프로미스는 자바스크립트 비동기 처리에 사용되는 객체입니다. 프로미스를 이용하면 비동기적 처리의 결과값이나(성공시) 에러값(실패시)을 핸들러(콜백 함수)와 연결하여 처리할 수 있도록 도와줍니다.

 

프로미스를 이해하기 전 꼭 비동기, 콜백 함수에 관한 이해를 먼저 해주세요.

Promise 사용하기

let getData = (callback) => {
    axios.get('url', (res) => {
        callback(res);
    });
}

getData(function(data){
    console.log(data);
});

위 코드는 axios를 이용해 간단하게 데이터를 받아오는 코드입니다. API를 이용하여 url에 해당하는 데이터를 get요청하게 되고, 응답으로 오는 데이터를 콜백함수로 받아 다시 callback이라는 콜백 함수를 실행시키죠. 응답이 완료되면 화면에 데이터가 표시 될 것입니다.

 

위 코드를 프로미스를 이용해 바꾸면 다음과 같습니다.

let getData = () => {
    return new Promise((resolve, reject) => {
        axios.get('url', (res) => {
           resolve(res);
        });
    });
}

getData().then((data) => {
    console.log(data);
});

getData함수가 callback함수를 직접 호출하는 것이 아니라, 프로미스 객체를 리턴하도록 수정되었습니다.

이렇게 promise객체를 리턴하도록 수정한 함수는 getData().then()과 같은 구조로 then을 이용하여 완료 처리를 할 수 있습니다. 

Promise의 상태

프로미스는 다음과 같은 3가지 상태를 가지고 있습니다.

 

  • Pending: 초기 상태, fulfilled나 rejected가 되지 않은 상태이다.
  • fulfilled: 비동기적 동작이 성공적으로 완료된 상태
  • rejected: 비동기적 동작이 실패한 상태

즉 프로미스 객체는 비동기적 처리를 실행한 뒤 요청을 기다리는 대기상태(Pending)가 됩니다.

이후 프로미스 객체는 비동기적 처리의 결과에 따라 fulfilled 또는 rejected(error)의 상태로 변화하게 됩니다.

 

이후 관련된 핸들러가 Promise의 then메소드나 catch메소드에 의해 불립니다.

Promise의 상태 변화

new Promise();

위와 같이 Promise를 생성자로 호출하면 Pending상태가 됩니다.

Promise생성자를 호출할 때 콜백 함수를 선언할 수 있도록 되어있는데, 인자는 각각 resolve, reject입니다.

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

resolve와 reject는 호출시 각각 fulfilled와 rejected로 Promise의 상태를 바꿔주게 됩니다.

즉, 비동기적 처리를 진행하고 로직에 따라 resolve나 reject를 실행하도록 하여 Promise의 상태를 바꾸는 것이죠.

let getData = () => {
    return new Promise((resolve, reject) => {
        let data = 100;
        resolve(data);
    });
}

getData().then(result => {
    console.log(result); // 100
}).catch(error => {
    console.log(error);
});

예시를 위해 비동기가 아닌 코드를 처리했습니다. Promise메서드를 호출하며 data에 100을 넣고 resolve를 호출하며 data를 넘겨줬습니다.

 

resolve는 Promise의 상태를 fulfilled로 변경해줍니다. 이렇게 상태가 fulfilled로 변경되면 아래의 then메소드에 의해 콜백 함수가 불리게 되고 인자에 넘겨준 값이 들어오게 됩니다. 

 

따라서 then의 콜백함수로 들어온 result의 값은 resolve에 넘겨준 값이 되어 100이 출력이 되는 것이죠.

 

그리고 위의 Promise를 만드는 과정에서 reject가 불리거나 에러가 발생하는 경우 Promise의 상태는 rejected로 변화하게 됩니다. 그리고 catch메서드에 의해 콜백 함수가 불리게 되죠.

 

이렇게 동작하는 것이 바로 Promise의 전체적인 동작 방법입니다.

Promise 비동기 처리 예제

배운 내용을 바탕으로 axios통신 코드를 만들어보자.

let getData = () = > {
    return new Promise((resolve, reject) => {
        axios.get('url', (res) => {
            if(res){
                resolve(res);
            } 
            reject(new Error('request failed'));
        });
    });
}

getData().then((data) => {
    console.log(data);
}).catch((err) => {
    console.error(err);
});

비동기적 axios 통신의 결과를 잘 받아온다면, resolve가 호출되어 then메서드가 실행이 될 것이고, 데이터를 받아오지 못했다면 reject가 실행되어 error메세지가 출력 될 것이다. 이렇게 프로미스를 이용한 코딩이 가능하다.

Promise Chaining

프로미스의 특징은 여러 개의 프로미스를 연결하여 사용할 수 있다는 점이다.

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 2000);
}).then((result) => {
    console.log(result); // 1
    return result + 10;
}).then((result) => {
    console.log(result); // 11
    return result * 10;
}).then((result) => {
    console.log(result); // 110
}).catch((err) => {
    console.error(err);
});

위와 같이 then으로 setTimeout으로 resolve를 호출 한 뒤, then을 연속적으로 연결하여 사용할 수 있다.

 

이런식으로 여러가지 비동기 처리 (사용자 인증, 데이터 요청, 화면 출력)을 묶어서 처리할 수 있으며, .then을 이용해 콜백 지옥 역시 회피할 수 있는 것이다.

Promise 에러 처리 방법

비동기적 코드를 promise로 처리하게 되면, 여러 상황에 따라 에러가 발생할 수 있습니다.

에러 처리 방법에는 크게 2가지 방법이 있습니다.

1. then()의 2번째 인자로 에러를 처리

getData().then(
    successHandler,
    errorHandler
);

2. catch()를 이용

getData().then().catch();

2가지 방법 모두 reject() 메서드가 호출되어 rejected상태가 되는 경우 잘 동작합니다.

Promise 에러 처리는 가급적 catch()를 이용

그러나 Promise의 에러를 처리할 때 가급적이면 catch()를 사용하는 것이 좋습니다.

이유는 다음과 같습니다.

let getData = () => {
    return new Promise((resolve,reject) => {
        resolve(1);
    });
}

getData().then((result) => {
    console.log(result);
    throw new Error('Error in then()'); // Uncaught
}, (err) => {
    console.log('error!');
});

위와 같이 작성한 경우 두 번째 인자로 들어간 함수는 reject가 발생하면 잘 잡아내지만, then의 첫 번째 콜백 함수 내부에서 발생한 에러는 제대로 잡아낼 수 없습니다.

 

하지만 똑같은 오류를 catch로 처리하면 잘 동작합니다.

let getData = () => {
    return new Promise((resolve,reject) => {
        resolve(1);
    });
}

getData().then((result) => {
    console.log(result);
    throw new Error('Error in then()');
}).catch((err) => {
    console.log('Error!');
});

위와 같이 catch로 사용하는 경우 then메서드 콜백 함수 내부의 에러 역시 잘 잡아냅니다.

따라서 가급적 catch()를 이용하여 에러를 처리하는 것이 좋습니다.

 

다음 시간에는 async, await에 대해 살펴보겠습니다

출처

- developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

- joshua1988.github.io/web-development/javascript/promise-for-beginners/