티스토리 뷰


로고 출처 : Something about Javascript Promises


번역할 글이 마음에 드는 것이 마땅히 없는 관계로.. 오늘은 개발하면서 고민했던 내용에 대해 써보려고 합니다.

ES6를 다루는 것은 아니므로 ES5 문법으로 작성하고 크롬에서 테스트했습니다.


Promise 는 q 나 async 와 같은 javascript library 로 제공되기 시작했으며 ES6 부터는 언어 레벨에서 제공하는 기능입니다.

상대적으로 가독성이 떨어지는 callback 패턴을 대체하여 async 함수도 sync 함수인 것 같은 flat 한 모양으로 코드를 작성하는 것이 큰 장점이라고 생각합니다.


*2, *3, *4 를 하는 3개의 함수를 예로 들어봅니다.


결과를 넘겨받는 sync 함수는 순차적인 실행에 문제가 없죠. 그냥 쓰면 됩니다.

var sync1 = function(param){ return param*2; }
var sync2 = function(param){ return param*3; }
var sync3 = function(param){ return param*4; }
 
var start = 1;
console.log(sync3(sync2(sync1(start)))); // 24


async 함수 3개가 있다고 하면 일반적으로는 이런 방식으로 쓰게 되죠.

var async1 = function(param, callback) { callback(param*2); }
var async2 = function(param, callback) { callback(param*3); }
var async3 = function(param, callback) { callback(param*4); }

var start = 1;

async1(start, result => {
	async2(result, result => {
		async3(result, result => {
			console.log(result); // 24
		});
	});
});


문제는... 함수가 복잡해지면서 들여쓰기에 의해서 코드가 알아보기 힘들어집니다. 예전에 작성한 코드 중에 이런 게 있었네요..... github 에서 본 모습입니다.

웹에서 봐도 스크롤을 하지 않으면 코드가 보이질 않아요.... 함수호출 뿐 아니라 if, switch 가 엮이면서 알아보기 힘든 코드가 되지요 ㅎㅎㅎㅎ


이때 좀 더 코드를 체계적으로 작성하도록 Promise 를 사용할 수 있습니다.

Promise 는 사용하는 방식이 크게 두 가지로 볼 수 있습니다. new Promise(function(resolve, reject)) 를 사용하는 패턴과 Promise.resolve(function()) 로 시작하는 패턴이 있죠. 처음엔 두 가지 방식의 차이를 이해하지 못했습니다. 그냥 상황에 맞게 이래저래 써보게 되었고... 사용 측면에서 약간의 차이를 발견했는데 그게 오늘 글의 요점입니다 ㅎㅎㅎ


new Promise 패턴을 사용하면 아래와 같이 쓸 수 있습니다.

function async1 (param) {
	return new Promise(function(resolve, reject) {
		resolve(param*2);
	});
}
function async2 (param) {
	return new Promise(function(resolve, reject) {
		resolve(param*3);
	});
}
function async3 (param) {
	return new Promise(function(resolve, reject) {
		resolve(param*4);
	});
}

var start = 1;
async1(start)
	.then(async2)
	.then(async3)
	.then(result => {
		console.log(result); // 24
	});


함수 선언 코드가 좀 길어지긴 했지만... 함수 사용 부분은 좀 더 명확해졌습니다.

같은 내용을 Promise.resolve() 로 사용하면 아래와 같죠.

function async1 (param) {
	return Promise.resolve(param*2);
}
function async2 (param) {
	return Promise.resolve(param*3);
}
function async3 (param) {
	return Promise.resolve(param*4);
}

var start = 1;
async1(start)
	.then(async2)
	.then(async3)
	.then(result => {
		console.log(result); // 24
	});


왠지 return new Promise() 보다 간결한 것 같습니다. new Promise 에서 받는 reject 를 처리하지 않는 경우에는 더욱 Promise.resolve() 패턴이 좋아 보입니다. 그래서 저도 이런 식으로 사용을 대부분 하던 중에 문제가 생겼습니다 ㅎㅎ

Promise 함수 안에 async 함수가 있으면 어떻게 해야 할까요.... Promise 는 then() 함수를 포함하는 thenable 객체를 반환하기 때문에 then() 이나 catch() 로 받아서 연속적인 동작을 하는 구조입니다. 다른 말로 하면, Promise 에 의해 thenable 객체가 반환되지 않았다면 Promise 종료 타이밍이 애매해질 수 있습니다.

xhr 예제를 한 번 봅시다.

(이 예제는 크롬의 cross origin 제약을 해제하고 실행했습니다.)

function request (param) {
	return Promise.resolve()
		.then(function () {
			var xhr = new XMLHttpRequest();

			xhr.open('GET', 'http://google.co.kr/', true);
			xhr.onreadystatechange = function () {
				if (xhr.readyState == 4 && xhr.status == 200) {
					return Promise.resolve(xhr.response);
				}
			};
			xhr.send(null);
		});
}

Promise.resolve()
	.then(request)
	.then(result => {
		console.log('ok');
		console.log(result);
	});

위 코드는 http://google.co.kr에 GET 요청을 보낸 뒤 받아서 결과를 텍스트로 출력하는 예제입니다. Promise 안에서 readyState와 status를 확인해서 Promise.resolve()를 리턴하므로 얼핏 보면 돌아갈 것 같습니다만... 제대로 동작하지 않습니다. 콘솔에 찍히는 result는 undefined 입니다.


request() 함수에서 xhr.send() 를 실행하면 해당 컨텍스트에서의 작업은 끝나고 Promise 컨텍스트도 종료됩니다. xhr이 완료되었을 때 반환하는 객체를 받을 타이밍에는 이미 이전 함수가 끝나있기 때문이죠.

이 코드는 new Promise()를 써서 이렇게 바뀌어야 합니다.

function request (param) {
	return new Promise(function(resolve, reject) {
		var xhr = new XMLHttpRequest();

		xhr.open('GET', 'http://google.co.kr/', true);
		xhr.onreadystatechange = function () {
			if (xhr.readyState == 4 && xhr.status == 200) {
				resolve(xhr.response);
			}
		};
		xhr.send(null);
	})
}

Promise.resolve()
	.then(request)
	.then(result => {
		console.log('ok');
		console.log(result);
	});

2번 라인에서 생성한 Promise 객체의 함수 실행 컨텍스트를 유지하기 위해 Promise.resolve() 대신 new Promise()를 사용하고 최종 콜백 지점인 8번 라인에서 resolve()를 실행해줍니다. 기존의 callback 사용 방식과 비슷하다고 보면 간단합니다.


이렇게 사용하면 Promise 실행 컨텍스트를 벗어나지 않고 원하는 결과를 얻을 수 있습니다.


비슷하게 ajax 콜을 요청하거나 DB에서 데이터를 가져올 때와 같이 async 함수를 감싸는 경우는 new Promise()를 사용하면 됩니다.

처음 Promise를 사용할 때는 왜 같은 흐름의 함수가 두 개로 제공될까 이상했는데, 사용하다보니 return Promise.resolve()와 new Promise()는 사용 방법이 약간 다릅니다. return Promise.resolve()는 sync 로직 흐름에서, new Promise()는 sync는 물론, async 로직 흐름에서도 사용할 수 있습니다.

요새 모듈은 사용할 때 Promise를 지원하는 경우가 종종 있습니다. 좀 더 많은 모듈이 Promise를 지원하기를 바래봅니다 ㅎ

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함