티스토리 뷰


로고 출처 : 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를 지원하기를 바래봅니다 ㅎ

댓글
  • 프로필사진 BlogIcon InspiredJW Promise.resolve 가 sync 로직에서만 쓰일 수 있는지 몰랐네요. 잘 보고 갑니다 ^^ 2016.08.05 17:57 신고
  • 프로필사진 BlogIcon 한장현 함수의 실행 스코프 안에서 끝내야 해서 그런 것으로 보입니다 ㅎㅎㅎ 2016.08.05 17:58 신고
  • 프로필사진 ㄲㅂ sync면 콜백을 왜쓰나요 프로미스패턴도 필요없을거같은데 2016.10.17 11:49 신고
  • 프로필사진 BlogIcon 한장현 모든 함수가 sync를 보장한다면 그냥 써도 되겠네요. 다만, sync3(sync2(sync1(start))) 와 같은 표현을 sync 1, 2, 3의 순서로 쓰는 것은 가독성 향상에 도움이 된다고 봅니다. 2016.10.17 12:55 신고
  • 프로필사진 highdali 하... 이해가 쉬웠어요.. ㄱㅅㄱㅅ 2017.03.09 04:26 신고
  • 프로필사진 WhiteWolf 설명 감사합니다.
    2017.04.05 08:50 신고
  • 프로필사진 1234 뭔데 어렵냐; 뇌정지옴 2017.05.19 23:08 신고
  • 프로필사진 5555 항상 프로미스 볼때 마다 2가지로 나눠쓰는지 궁금했는데 이 글보고 알게 됬네요 친절하신 설명 감사합니다 2017.09.05 21:30 신고
  • 프로필사진 나은성 쉽다는말을 끝내 믿으려 했건만 ㅠㅠ
    결론은 '어렵다' 로군요.
    2018.09.13 22:46 신고
  • 프로필사진 Nobody 뭔가 틀리게 사용하시고 이해하셨네요.
    `new Promise` VS `Promise.resolve` 로 비교하실게 아니라
    `new Promise` VS `Promise.then` 으로 비교하셨어야 맞을것 같습니다.

    1. 비교가 잘 못됨
    `new Promise`는 비동기 함수를 인자로 받고,
    `Promise.then`은 동기 함수를 인자로 받습니다.

    2. "xhr.onreadystatechange 내부에서의 return Promise.resolve(xhr.response)"
    위 코드는 xhr 내부로 return 하는거지 의도하신대로는 작동하지 않는 코드입니다.
    2018.12.29 15:02 신고
  • 프로필사진 BlogIcon 한장현 안녕하세요. 한장현입니다.
    댓글 남기신 것에 대해 궁금한 점이 있어 글 남깁니다.

    1.
    `new Promise` VS `Promise.resolve` 로 비교하실게 아니라
    `new Promise` VS `Promise.then` 으로 비교하셨어야 맞을것 같습니다.

    => new Promise 와 Promise.resolve 는 Promise 체인을 시작하는 형태를 비교하기 위해 선택한 두 가지 방식입니다.
    Promise.prototype.then 을 의도하신 거라면 이 메소드는 Promise 체인을 시작하는 메소드가 아닐 뿐더러, new Promise로 만든 체인에도 존재하는 메소드이기 때문에 비교의 대상이 되기 어렵다고 생각했습니다.

    2.
    `new Promise`는 비동기 함수를 인자로 받고,
    `Promise.then`은 동기 함수를 인자로 받습니다.

    => new Promise는 fulfilledHandler와 errorHandler를 인자로 받는 콜백 스타일이기 때문에 비동기 함수를 인자로 받는다고 볼 수 있습니다.
    하지만 Promise 체인에 존재하는 then 메소드가 동기 함수를 인자로 받는다는 것은 이해가 가지 않네요.

    아래와 같은 코드는 1초 후에 setTimeout() 함수가 실행되어야 then() 함수의 실행이 완료됩니다. 이 때 then() 으로 전달된 함수가 동기 함수여야 한다는 제약은 스펙에서도 찾아볼 수가 없네요.
    Promise.resolve()
    .then(async () => {
    await new Promise((resolve) => {
    setTimeout(() => {
    console.log('done()');
    resolve();
    }, 1000)});
    });

    then() 함수가 동기 함수를 인자로 받아야 한다는 내용은 어디에서 찾아볼 수 있을까요?

    3.
    2. "xhr.onreadystatechange 내부에서의 return Promise.resolve(xhr.response)"
    위 코드는 xhr 내부로 return 하는거지 의도하신대로는 작동하지 않는 코드입니다.

    => xhr.onreadystatechange 안에서 사용한 resolve는 request() 함수에서 반환하는 new Promise() 객체에 전달된 fulfilledHandler 입니다.
    예제 코드에서 xhr 내부로 리턴하는 부분은 없는데 어떤 부분을 말씀하시는 걸까요?
    캡처를 올려두었듯이, CORS 해제 후 동작도 확인한 예제입니다.

    제 생각과 다르게 생각하시는 부분이 있다면 댓글 부탁드립니다^^
    2018.12.29 23:53 신고
  • 프로필사진 Nobody 1. 동기 함수라고 쓴 제 설명이 잘못되었네요.
    틀린예시로 꼽아주신, 동기함수내에서 `XMLHttpRequest`비동기활용은 오히려 이해하기 어려운것 같습니다.
    이 예시에서의 promise.then을 동기활용을 강조하려던 제 틀린 설명이었습니다.

    2. 정성스럽게 글 써주셨는데, 초보분들이 이해하기 더 쉽게 하기 위해
    - xhr 틀린 예제(promise.resolve)소개
    - 이렇게 쓰면 동기함수에서 비동기로 활용하는 형태라 작동되지 않는다. 때문에
    -- new Promise 를 쓰거나
    -- promise.then(async(){ await axios.get(url); }) 형태로 작성 (굳이 axios를 쓰기는 애매할 수는 있겠지만요)
    하시면 어떨까 싶습니다.

    3. google 에서 promise resolve 로 검색해서 나오는 첫 블로그글이어서 보게되었는데
    의도하신것과는 다르게 오히려 이해가 어려웠네요.
    특히 틀린 예시에 대해 "Promise.resolve()를 리턴하므로 얼핏 보면 돌아갈 것 같습니다만" 이 설명이 더 혼란스러웠습니다.
    예제가 틀린 이유가 더 보강되면 이해가 좋아질것같습니다.

    당연히 저보다 훨씬 잘하실것 같은데, 괜시리 태클드린것 같네요. 송구스럽습니다.
    2018.12.31 13:40 신고
  • 프로필사진 BlogIcon 한장현 덕분에 Promise에 대해서 좀 더 자세하게 찾아봤습니다 ㅎㅎ

    그리고 의견 주신 내용은 다음 글을 작성할 때 참고하겠습니다.

    감사합니다^^
    2018.12.31 17:33 신고
댓글쓰기 폼
공지사항
Total
305,731
Today
15
Yesterday
95
링크
«   2019/01   »
    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 31    
글 보관함