Promise 파헤치기

9999·2022년 2월 15일
0

JavaScript

목록 보기
8/13

탄생 이유


저같은 경우 비동기 함수 처리를 하다보면 의도한대로 나오지 않는 경우가 허다합니다. 그러면 항상 조건문을 걸고 콜백함수 달기 바빴죠..

const 요청하기 = function() {
	const xhr = new XMLHttpRequest();
	if (xhr.status === 200) {
		successCallback();
	} else {
		failedCallback();
	}
}

이런 코드가 하나둘 쌓이다 보면 어느새 암호문 해독수준인 코드가 눈앞에 펼쳐질 겁니다.

const 요청하기 = function() {
	const xhr = new XMLHttpRequest();
	xhr.open("GET", url);
	xhr.send();
	
	xhr.onload = () => {
		if (xhr.status === 200) {
			function successCallback(JSON.parse(xhr.data)) {
				if (true) {               // 콜백함수가 성공하면
					successCallback(() => { // 더 불러와주세요..
							callback(() => ~~~); // *그만...*
						});
				} else {
					failedCallback(() => ~~~);
				};
			};
		} else {
			failedCallback();
		};
	};
};

위 코드를 보시면 콜백함수를 여러번 불러오는 끔찍한 광경을 보실 수 있습니다. 작성하는 사람도 이게 맞나 하면서 작성하고 보는 사람도 그냥 절레절레하게 만드는 코드입니다.

그리고 위 코드는 아까 말했다시피 비동기 함수라서 후속 처리된 결과를 확인하기가 까다롭습니다.

const 요청하기 = function(url) {
	const xhr = new XMLHttpRequest();
	xhr.open("GET", url);
	xhr.send();
	
	xhr.onload = () => {
			if (xhr.status === 200) {
					successCallback();
				}; 
	};

	const res = get(url);
	console.log(res); // undefined
};
// 콘솔창에서 get요청한 결과를 볼 수 없습니다.

자바스크립트는 비동기 함수라고 생각되면 시간이 걸릴거라고 판단되기 때문에 태스크 큐에 보관했다가 나머지 코드가 다 실행되고 나서 콜 스택에 푸시됩니다. 그래서 console.log가 먼저 실행되고 종료된 다음 get요청이 이루어 집니다. 이러한 이유로 콘솔창에는 undefined가 출력됩니다.

그래서 get요청안에서 후속 처리를 해야 결과를 볼 수 있습니다.

get("url1", a => {
	successCallback();
});

저렇게 했을 때 후속처리가 끝나면 좋겠지만 안 끝날경우 콜백 함수 안에 콜백 함수를 또 만들어야겠죠? 그러면 위에서 봤던 꼬리물기 코드를 보실 수 있을겁니다.

이렇게 예외 처리를 끝없이 하고 가독성도 떨어지는 문제점을 해결하기 위해 프로미스(Promise)가 나왔습니다.

프로미스란?


프로미스는 비동기 처리 상태와 처리 결과를 관리하는 객체입니다. new 연산자와 함께 Promise객체를 생성하고 비동기 처리를 수행할 콜백 함수를 resolve, reject인자로 받습니다.

const 요청하기 = function(url) {
	return new Promise((resolve, reject) => {
		const xhr = new XMLHttpRequest();
		xhr.open("GET", url);
		xhr.send();
		
		xhr.onload = () => {
			if (xhr.status === 200) {
				resolve(JSON.parse(xhr.response));
			} else {
				reject(new Error(xhr.status));
			};
		});
	};
};

눈치 채셨듯이 resolve는 성공시 비동기 처리 결과를 반환하고 reject는 실패시 처리 결과를 반환합니다.

resolve된 상태fulfilled상태라고 하고 reject된 상태rejected상태라고 합니다. 둘 다 아닌 상태는 pending상태라고 합니다.

status: pending — resolve(value) → status: fulfilled, result: value

status: pending — reject(error) → status: rejected, result: error

비동기 처리가 수행된 상태는 settled상태라고 합니다.

status → settled

후속 처리 메서드


프로미스는 비동기 처리 상태가 되면 후속 처리를 해줘야 합니다. 예를 들어, 프로미스가 fulfilled상태가 되면 프로미스의 처리 결과를 가지고 무언가를 해야 하고, rejected상태가 되면 에러를 가지고 에러 처리를 해야 합니다.

then

then메서드는 두개의 인수를 가지는데 첫 번째 인수는 비동기 처리가 성공했을 때를 처리하고 두 번째는 실패했을 때 호출되는 콜백 함수 입니다.

new Promise(resolve => resolve('fulfilled'))
.then(a => console.log(a), e => console.error(e));

catch

catch메서드는 프로미스가 rejected상태인 경우만 호출됩니다. 비동기 처리가 실패했을 때 실행됩니다.

new Promise(reject => reject(new Error('rejected')));
.catch(err => console.log(err));

then메서드의 두번째 인자를 사용해도 되지만 코드의 가독성때문에 catch메서드로 에러처리를 하는 것을 권장합니다.

finally

finally메서드는 성공 여부 상관없이 무조건 실행되는 메서드입니다.

new Promise(resolve => resolve("fulfilled"))
.then(a => console.log(a))
.catch(err => console.log(err))
.finally(() => console.log('finally'));

then또는 catch가 실행되고 마지막엔 항상 finally가 실행됩니다.

프로미스 정적 메서드


Promise.resolve / Promise.reject

이미 존재하는 값을 래핑하여 프로미스를 생성하기 위해 사용합니다.

const resolvedPromise = Promise.resolve([1, 2, 3]);
resolvedPromise.then(console.log); // *[1, 2, 3]*

const rejectedPromise = Promise.reject(new Error('Error'));
rejectedPromise.catch(console.log) // *Error*

Promise.all

여러 개의 비동기 처리를 모두 병렬처리할 때 사용됩니다.

const data1 = () => {
	new Promise(resolve => setTimeout(() => resolve(1), 2000));
};
const data2 = () => {
	new Promise(resolve => setTimeout(() => resolve(2), 3000));
};
const data3 = () => {
	new Promise(resolve => setTimeout(() => resolve(3), 1000));
};

Promise.all([data1(), data2(), data3()])
.then(console.log)  // *[1, 2, 3] 약 3초*
.catch(console.log)

Promise.all메서드는 모든 프로미스가 모두 fulfilled상태가 되야 종료됩니다. 따라서 가장 늦게 fulfilled되는 프로미스의 처리 시간보다 약간 더 걸립니다. 위 코드를 예로 들면 3초보다 조금 더 시간이 들겠네요.

rejected상태가 되면 가장 먼저 reject된 프로미스를 catch메서드의 인자로 전달합니다.

// 3

Promise.race

가장 먼저 fulfilled되는 프로미스의 처리 결과를 resolve하는 새로운 프로미스를 반환합니다.

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

reject상태는 Promise.all과 같습니다.

Promise.allSettled

이 메서드는 프로미스를 요소로 갖는 배열 등의 이터러블을 인수로 전달받습니다. 그리고 전달받은 프로미스가 모두 settled상태가 되면 fulfilled상태, rejected상태 처리 결과를 모두 배열로 반환합니다.

Promise.allSettled([
	new Promise(resolve => setTimeout() => resolve(1), 1000),
	new Promise(reject => setTimeout(() => reject(new Error("Error")), 2000))
]).then(console.log);

// [
// {status: "fulfilled", value: 1}
// {status: "rejected", reason: Error: Error at <anonymous>}
// ]

이 기능은 ES11(ECMAScript 2020)에 도입되어 인터넷 익스플로러를 제외한 대부분의 브라우저에서 지원합니다.

애매하거나 틀린 부분이 있으면 피드백 감사하겠습니다.

0개의 댓글