Web Programming

Promise 비동기 처리의 이해

안녕하세요, 씨앤텍시스템즈 강희수 연구원입니다.

 

웹 서비스를 클라이언트에 제공하기 위해서는 다양한 데이터를 서버로부터 주고받게 됩니다. 이때, 항목, 필요성, 양 등에 따라 그 데이터가 서버로부터 수신되는 시간은 천차만별일 것입니다. 모든 데이터가 일련의 작업으로 이루어진다면, 데이터 수신에 병목현상이 생길 수 있고, 그로 인해 필요한 정보는 얻지도 못한 체 클라이언트는 무한한 기다림 속에 빠지게 될 것입니다.

 

이러한 문제로부터 필요한 작업을 필요한 때에 시행할 수 있는 비동기 처리 방법, 그중에서도 깔끔하고 명확한 작성이 가능한 자바스크립트의 Promise 객체를 이용한 비동기 처리에 대해 알아보도록 하겠습니다.


생성

먼저, Promise 객체는 생성자를 통해 만들 수 있습니다.

 

Promise는 Resolve와 Reject를 매개변수로 하는 executor라는 함수를 인자로 사용하며, 아래와 같이 생성자를 통해 만들 수 있습니다.

New Promise((resolve, reject) => {})

이때, 생성자를 통해서 Promise 객체를 만드는 순간 pending 상태로 진입하여, 비동기 작업의 이행과 실패를 판단하게 되는데,  

executor 매개변수 중 하나인 resolve를 실행하게 되면 fulfiled 즉, 비동기 작업이 성공 / 이행되었다는 상태를 나타내고, 

reject를 실행하게 되면, rejected 즉, 비동기 작업이 거부 / 실패 / 거절되었다는 상태를 나타냅니다.


Resolve

예를 들어, 3초 후에 이행(fulfiled)되는 Promise* 객체를 리턴하는 함수인 promise*를 생성해 봅니다. (*대문자 / 소문자 구분) 

const promise = new Promise ((resolve, reject) => {
	setTimeout(() => {
    	resolve();
    }, 3000)
});

 

하지만, promise 함수를 호출했는데, 비동기 처리가 정상적으로 처리되었다는 것을 어떻게 알 수 있을까요?

 

그것은 아래와 같은 then() 메서드를 통해 알 수 있습니다.

promise.then(() => {
	console.log('3000ms (3초) 후에 이행되었고, 그로 인해 then이 실행되었습니다.')
});

resolve가 처리되어 비동기 처리가 이행되면, then이 갖는 callback함수가 실행됩니다.


Reject

그렇다면, 비동기 처리가 실패했을 때는 어떻게 알려줄까요?

 

이는 Promise의 매개변수 중 reject를 호출하여 사용하게 됩니다.

function sendingParam () {
	return new Promise ((resolve, reject) => {
    	setTimeout(() => {
        	reject('에러 - 데이터 없음');
        },1000)
    })
}

 

또한, catch()메서드를 통해 비동기 처리가 실패했을 때 호출되는 callback 함수가 실행됩니다.

 

sendingParam().then(message => {
	console.log('fulfiled' + message);
}).catch(reason => {
	console.log('rejected' + reason);
})

위처럼 Then() catch() 메서드는 각각 연속하여 작성되어 해당하는 비동기 처리 결과에 따라 함수를 호출합니다. 또한 resolve reject를 실행 시, 인자를 넣어 실행하면, then catch 메서드에 인자로 받을 수 있습니다.


Error

위처럼 Then() catch() 메서드는 각각 연속하여 작성되어 해당하는 비동기 처리 결과에 따라 함수를 호출합니다. 또한 resolve reject를 실행 시, 인자를 넣어 실행하면, then catch 메서드에 인자로 받을 수 있습니다.

 

function sendingErrorObj () {
	return new Promise ((resolve, reject) => {
    	setTimeout(() => {
        	reject(new Error('bad'));
        },1000)
    })
};

sendingErrorObj().then(message => {
	console.log('fulfiled');
}).catch(error => {
	console.log(error);
});

위 함수의 실행 결과는 아래와 같습니다.


Finally()

다음으로, 비동기 처리의 성공과 실패 여부에 상관없이 실행되는 finally() 메서드에 대해 알아보도록 하겠습니다.

 

function promise () {
	return new Promise ((resolve, reject) => {
    	setTimeout(() => {
        	reject(new Error('bad'));
        },1000);
    })
};

promise().then(message => {
	console.log('fulfiled');
}).catch(error => {
	console.log(error);
}).finally(() => {
	console.log('end');
});

 

위의 코드와 같이 then(), catch() 메서드와 연속하여 finally() 메서드를 작성하게 되면,

Finally의 콜백함수는 promise의 성공 여부에 상관없이 아래와 같은 결과를 출력하게 됩니다.

 


연속하는 비동기 처리

then 함수에서 다시 Promise 객체를 리턴하는 방법을 통해 연속하는 비동기 작업을 처리할 수 있습니다.

 

예를 들어, 연속하는 비동기 작업에서 4초 후에 이행되는 콜백함수를 실행시켜 봅니다.

 

function promise() {
	return new Promise ((resolve, reject) => {
    	setTimeout(() => {
        	resolve();
        },1000);
    });
};


promise().then(() => {
	return paromise();
})
.then(() => promise())
.then(promise)
.then(() => {
	console.log('4000ms 후에 이행됩니다.');
});

 

위와 같이, 연속하는 각각의 함수에서 리턴 결과에 따라 다음 순서의 then 메서드가 갖는 콜백함수가 실행됨을 알 수 있습니다.

또한, 각각 다른 형태로 작성된 then 메서드는 같은 뜻으로 작성되었음을 알 수 있습니다.


다양한 Promsie 객체 생성 방법

지금까지 Promise 객체를 생성하기 위해 생성자를 이용했다면, 다음은 그 밖의 다양한 방법을 통한 Promise 객체 생성 방법에 대해 알아보도록 하겠습니다.

 

Promise 전역 객체

Promise 전역 객체 안의 resolve 함수를 실행하면서 Promise를 만드는 방법이 있습니다. 이는 다음과 같은 기본 형태로 작성합니다.

 

Promsie.resolve(value);

먼저, 아래와 같이 value Promise 객체이면, resolve then 메서드를 실행합니다.

 

Promise.resolve(
	new Promise((resolve, reject) => {
    	setTimeout(() => {
        	resolve('foo');
        },1000)
    })
).then(data => {
	console.log('Promise 객체인 경우, resolve된 결과를 받아 then이 실행됩니다.', data);
});

여기서 then 메서드는 Promise 객체가 resolve 된 후에 불리게 될 것입니다.

 

또는 아래와 같이, value가 Promise 객체가 아니면, value 인자를 내보내면서 then 메서드를 실행합니다.

Promise.resolve('foo').then(data => {
	console.log('value 가 Promise 객체가 아닙니다.', data')
});

 

위의 두 가지 경우는 아래와 같은 결과로 나타납니다.

 

같은 방법으로, Promise.reject(value)를 사용하면, catch로 연결된 콜백함수가 실행됩니다.

Promise.reject(
	new Promise((resolve, reject) => {
    	setTimeout(() => {
        	reject(new Error('에러));
        },1000)
    })
).catch(error => {
	console.log('1초 후에 거절죕니다', error);
});

Promise.reject(new Error('에러')).catch(error => {
	console.log(error)
});

위의 reject 결과는 아래와 같이 먼저 출력되는 에러와 1초 후 출력되는 에러를 확인할 수 있습니다.

 

 

Promise.all

이 방법은 하나 이상의 Promise 객체를 한 번에 실행하여 결과를 출력하는 방식입니다.

 

먼저, Promise 객체를 여러 개 생성한 후, 배열로 만듭니다.

, Promise.all에 인자로 넣어 실행되고,

, Promise 객체 하나하나가 모두 성공적으로 처리되면,

, Promise.all에 연결되어 있는 then 메서드 안의 callback 함수가 실행됩니다.

 

아래 실제 코드를 살펴보도록 합니다.

function promise(ms) {
	return new Promise ((resolve, reject) => {
    	setTimeout(() => {
        	resolve(ms);
        }, ms);
    });
};

Promise.all([promise(1000), promise(2000), promise(3000)]).then(message => {
	console.log('모두 fulfiled된 이후에 실행됩니다.', message);
});

위의 코드를 통해 알 수 있는 것은 3초가 지난 후에 then이 실행되어 아래와 같은 결과를 얻을 수 있습니다.

 

 

이러한 방법은 비동기 작업 여러 개를 동시에 시작해서 모든 작업이 완료된 후 처리해야 할 경우에 사용합니다.

 

Promise.race

이 방법은 하나 이상의 Promise 객체 그룹에서 가장 먼저 실행되는 비동기 작업의 결과를 출력합니다.

 

function promiseRace (ms) {
	return new Promise((resolve, reject) => {
    	setTimeout(() => {
        	resolve(ms);
        },ms);
    })
};

Promise.race([promiseRace(1000), promiseRace(2000), promiseRace(3000)]).then(ms => {
	console.log('가장 먼저 실행된 Promsie 객체입니다.', ms);
});

위의 코드를 통해 가장 먼저 실행된 Promise 객체는 PromiseRace(1000)임을 확인할 수 있습니다.


마무리하며,

 

지금까지 Promise를 통한 비동기 처리를 알아보았습니다. 이러한 Promisethen 메서드를 통해 콜백이 중첩되어 방생하는 콜백 지옥에서 벗어나게 해주면서, 연속하는 비동기 작업이 가능하게 합니다. 또한 비동기 작업으로 값, 위에서 살펴본 value를 다룰 수 있다는 장점이 있습니다.


(참고자료)

 

728x90