Promise

·2023년 1월 29일
0

개발 지식

목록 보기
13/96
post-thumbnail

Promise에 대해 설명하시오

키워드
비동기, 콜백으로 인한 문제 개선

프로미스는 자바스크립트에서 비동기 처리에서 사용되는 객체로서, 기존까지 사용되던 비동기 처리의 문제점을 개선하기 위해 등장했다.

문제점 1. 콜백지옥

비동기 처리의 대표적인 해결 방식으로, 하나의 이벤트가 정상적으로 호출되었을 때, 이후 이벤트가 작동할 수 있도록 하는 매커니즘 이다.

function getData() {
    var response = {};
    $.get('http://domain/products/1', fucntion(response) { response = response; });
    return response;
}

위의 코드는 서버에 GET 요청을 하여, 데이터를 가져오는 코드이다. 단 해당 코드는 undefined이 나올 수 밖에 없다. 이유는 response 결과를 가져오기도 전에, 함수가 리턴되기 때문이다. 비동기 통신이 아직 익숙하지 않은 사람이라면 이러한 통신 흐름을 개발자가 원하는대로 제어하는 것이 쉽지 않다. 다음 코드를 보자.

function getData(callbackFunc) {
    $.get('http://domain/products/1', fucntion(response) { callbackFunc(response) });
}

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

위의 코드가 처음의 undefined 코드를 콜백으로 구현한 것이다. 이처럼 이후에 실행할 함수를 매개변수로 등록하고, 부모함수가 먼저 모든 작업이 종료된 경우, 매개변수 호출로 리턴하여 그 다음 작업이 이어지도록 하는 것이다. 단 이 콜백이라는 해결방식은 다음과 같은 문제를 발생하였다.

step1(function (value1) {
    step2(function (value2) {
        step3(function (value3) {
            step4(function (value4) {
                step5(function (value5) {
                    step6(function (value6) {
                        // Do something with value6
                    });
                });
            });
        });
    });
});

프로젝트를 구현하다보면, 한번의 비동기 작업만 이루어지지 않는다. 예를들어 로그인 이벤트를 구현한다면, 아이디와 비밀번호가 일치하는 지 보내는 작업과 유저의 역할이나 데이터를 가져오는 작업도 이루어질 수있다. 이처럼 여러번이 호출이 필요한 경우에 코드가 위와 같이 상당히 복잡해진다.

문제점2. 에러를 찾기 어려움.

콜백지옥에서 이어지는 문제이다. 비동기 이벤트 특성 상 실제 호출하기 전까지 해당 호출이 실패했는지 성공했는지를 우리는 알 수 없다. 따라서 성공했을 때의 콜백함수와 실패했을 때의 콜백함수를 만들어처리하는 것이 대중적으로 많이 사용하는 패턴이였다. 단 콜백지옥에 성공한 경우와 실패한 경우까지 나눠 처리해야하니, 가독성은 더더욱 안좋아질 수 밖에 없다. 또한 비동기 호출은 여러개이지만, 에러를 한번에 처리하는 경우도 생각해봐야한다. 다시 예를 들어 로그인하여 유저의 데이터를 가져오는 경우로 했을 때, 두개의 비동기 통신 모두 에러처리를 “로그인이 정상적으로 이루어지지 않았습니다. 다시 시도해주세요” 라고 일괄처리할 수 있다. 그럼에도, 똑같은 함수를 2번 작성해줘야하는 문제가 발생한다.

프로미스 특징.

위의 설명과 같이, 프로미스는 기존 비동기 통신에서 사용하던 콜백에서 나타나던 단점을 보완하기 위해 만들어졌다. 프로미스(약속) 이라는 말과 같이, 비동기 함수와 성공과 실패여부를 미리 보증 하는 컨테이너 객체를 반환하는 것이다.

프로미스는 총 3가지의 상태값을 가지고 있다. pending , fullfilled , rejected 이다.

  • pending (대기) : 이행이 되지 않은 상태, 아직 성공 실패 결과가 나타나지 않은 상태
  • fullfilled (이행) : 이행이 된 상태, 호출과정이 정상적으로 이루어져 성공적인 결과를 가져온 상태
  • rejected (거부) : 거부 상태, 호출과정에서 예외를 만나 실패 결과를 반환한 상태

Promise 의 가장 큰 특징은 반환 값 역시 Promise 객체로 반환한다는 것이다. 즉 프로미스간 체이닝이 가능하여, 이를 통해 콜백지옥 문제를 해결 할 수 있다.

new Promise().then(() => step1());
						 .then(resolve => step2(resolve));
						 .then(resolve => step3(resolve));
						 .then(resolve => step4(resolve));
						 .then(resolve => step5(resolve));
						 .catch((e) => {
						    if(e.message == "a") onErrorA();
								else if(e.message == "b") onErrorB();
								else onCommonError() 	
						  });

이처럼 모든 호출이 독립적으로 then 으로 나누어 처리되어, 코드의 순차성을 좀 더 쉽게 제어할 수 있을 뿐만 아니라, 어느 비동기 호출에서 에러가 일어났는지, 해당 에러가 난 경우 어떤 부분은 일괄 처리하고, 어떤 부분은 특정 처리를 할 것인지 등의 처리가 훨씬 간편해진다.

프로미스 메서드

Promise.resolve

Promise 함수의 매개변수 가운데 성공했을 때 호출되는 첫번째 매개변수를 호출한다. 해당 콜백함수는 프로미스로 래핑되여 반환이 된다.

const promise = Promise.resolve('success') // new Promise(resolve => resolve('success'))
promise.then(message => console.log('This is in the then ' + message))

Promise.reject

Promise 함수의 매개변수 가운데 실패했을 때 호출되는 두번째 매개변수를 호출한다. 해당 콜백함수는 프로미스로 래핑되여 반환이 된다.

const promise = Promise.reject('failed') // new Promise((resolve, reject) => reject('failed'))
promise.catch(error => console.log('This is in the catch ' + error))

Promise.finally

Promise 상태에 상관없이, 해당 Promise 가 이행 결과를 나타내는 경우, 무조건적으로 콜백 이벤트를 호출한다.

Promise.all

다수의 Promise 객체를 더욱 간편하게 처리하는 메서드로, Promise 객체로 채워진 이더러블 객체를 인자로 받는다. 만약 어떤 이벤트를 만드는 경우, 반환 데이터를 가져오는데 비동기 호출이 여러개 필요한 경우 사용한다.

프로미스가 담긴 이더러블 객체는 idx 에 따라 순차적으로 처리가 진행된다. 첫번째 프로미스가 종료된 후에 두번째 프로미스가 실행되고, 두번째 프로미스가 종료된 후에 세번째 프로미스가 실행이 되는 식이다.

각 프로미스의 결과값들은 하나의 배열에 담아 같이 리턴이 된다.

💥 역시 모든 결과값이 프로미스 객체로 래핑되어 나타납니다.

만약 이 과정 중 하나라도 실패하게 된다면, 실행 과정이 종료되고 Promise.reject 가 실행 된다.

// 여러개의 Promise 처리하기
const promise1 = () => new Promise(resolve => setTimeout(() => resolve(1), 1000))
const promise2 = () => new Promise(resolve => setTimeout(() => resolve(2), 2000))
const promise3 = () => new Promise(resolve => setTimeout(() => resolve(3), 3000))

promise1().then(result => {
    console.log(result) // 프로그램을 실행하고 1초뒤에 수행됨
    return promise2()
}).then(result => {
    console.log(result) // 프로그램을 실행하고 3초뒤에 수행됨 (1 + 2)
    return promise3()
}).then(result => {
    console.log(result) // 프로그램을 실행하고 6초뒤에 수행됨 (1 + 2 + 3)
})

// 여러개의 Promise를 Promise.all 을 사용하여 처리하기
Promise.all([
    new Promise(resolve => setTimeout(() => resolve(1), 1000)),
    new Promise(resolve => setTimeout(() => resolve(2), 2000)),
    new Promise(resolve => setTimeout(() => resolve(3), 3000))
]).then(console.log) // 프로그램을 실행하고 3초뒤에 실행됨
.catch(console.log)
// [1,2,3];

Promise.race

여러개의 Promise 를 처리한다는 것은 Promise.all과 동일하나, Promise.race 는 모든 Promise 가 동시에 실행이 되어, 가장 먼저 리턴되는 Promise 결과값을 resolve 로 반환한다.

Promise.race([
    new Promise(resolve => setTimeout(() => resolve(1), 1000)),
    new Promise(resolve => setTimeout(() => resolve(2), 2000)),
    new Promise(resolve => setTimeout(() => resolve(3), 3000))
]).then(console.log) 
.catch(console.log)
// 1

Promise.allSettled

여러개의 Promise 를 처리한다는 것은 Promise.all 과 동일하나, Promise.allSettled 의 경우, 하나의 프로미스가 rejected 상태가 되어도 수행을 종료하지 않고, 각 Promise 마다 수행 상태와 수행 결과값을 배열에 담아 resolve 로 반환한다.

Promise.allSettled([
    new Promise(resolve => setTimeout(() => resolve(1), 1000)),
    new Promise((resolve, reject) => setTimeout(() => reject(2), 2000))
]).then(console.log)
// [{ status: 'fulfilled', value: 1 },{ status: 'rejected', reason: 2 }]

참고
자바스크립트 Promise 쉽게 이해하기
비동기와 Callback, Promise, Async/await
[JavaScript] 프로미스(Promise)란

profile
새로운 것에 관심이 많고, 프로젝트 설계 및 최적화를 좋아합니다.

0개의 댓글