코기판

Javascript Promise 사용하기 본문

Javascript

Javascript Promise 사용하기

신코기 2019. 9. 26. 23:47

Javascript promise에 대해서 정리해보도록 하겠습니다.

 

 

요즘 인프라 업무가 바빠서 js는 손놓고 있다가, 오늘 보안 쪽 친구가 js promise에 대해 물어보길래

샘플 코드와 함께 정리해두면 두고두고 보기 좋을 것 같아 기록을 남겨둡니다. 

 

작년에 ipfs(InterPlanatery File System)과 관련한 과제를 진행한 적이 있었습니다. 

js code로 device를 제어해야 하는 기능이 있었는데, device의 응답을 기다려야 했기 때문에 시간이 많이 걸리는 기능이 있었습니다. 

그래서 이 promise기능을 유용하게 썼던 기억이 있습니다.

 

저는 처음에 개념을 잘 이해하지 못하고 사용했기 떄문에 많이 헤맸는데요, 

부디 이 포스팅을 보시는 분들께서는 저와 같은 시행착오를 겪지 않으시길 바랍니다. 

 

보통 promise를 설명하는데 setTimeout을 많이 이용합니다.

굳이 setTimeout이 아니더라고, xhr 리퀘스트처럼 응답을 기다려야 하는 기능이라고 생각하시면 이해가 쉬울 것 같습니다. 

 

결과를 명확하게 보여드리기 위해 여기에서는 setTimeout을 사용하도록 하겠습니다. 

 

먼저, 전형적인 callback 함수를 사용하는 방법입니다. 

let myCb = (param, doneFunc) => {
	window.setTimeout(() => {
      	doneFunc("result");
    }, 5000);
}

let callbackExample = () => {
	console.log(">>>> start callbackExample");

	myCb("this is callback", (res) => console.log("callback result is : " + res));

	console.log("<<<< end callbackExample");	
}

callbackExample();
// output

>>>> start callbackExample
<<<< end callbackExample
callback result is : result

화살표 함수를 써서 조금 간단해지긴 했지만, 만약 콜백 안에서 또 콜백을 호출해야 하는 경우가 생긴다면,

콜백 안에 콜백 안에 콜백 안에... 콜백 지옥이 만들어지는 것은 순식간입니다. 

 

그리고, 기다려서 받은 응답이 원하던 응답인지, 아니면 원하지 않는 응답인지를 callback안에서 확인해주어야 합니다.

myCb 함수는 콜백을 실행시켜주기만 할 뿐이죠 

 

다음은 비슷한 기능을 하는 함수를 promise로 구현한 예제입니다.

let myPromise = (param) => {
    return new Promise((resolve, reject) => {
        window.setTimeout(() => {
      	    if (param) {
      	    	console.log("myPromise done with RESOLVE status");
      		    resolve(true);
      	    }
      	    else {
      	    	console.log("myPromise done with REJECT status");
      		    reject(false);
      	    }
        }, 3000);
    });
}

let promiseExample = () => {
	console.log(">>>> start promiseExample");

	let p = myPromise(true)
	        .then((result) => console.log(result), (err) => console.log(err))
    console.log("<<<< end promiseExample");
}

promiseExample();
>>>> start promiseExample
<<<< end promiseExample
myPromise done with RESOLVE status
true

콘솔의 출력을 보면, 콜백을 사용했을 때와 마찬가지로 "<<<< end promiseExample" 이 promise의 성공 여부를 기다리지 않고 바로 실행되는 것을 볼 수 있습니다.

 

콜백과의 가장 큰 차이점은 promise에서는 resolve와 reject가 있다는 것입니다.

promise내부에서는 시간이 걸리는 일을 처리하고(여기에서는 setTimout) param이 true이면 true로 resolve하고, 

그 이외에는 false로 reject를 합니다. 

promise가 일의 결과에 대해서 통과 / 에러 를 판단는 부분입니다. 

 

resolve, reject 로 pass/fail을 결정짓고, ()안에 들어있는 값이 .then 함수로 전달됩니다. 

즉, resolve(true)이기 때문에 가장 마지막 줄의 console.log가 true 값을 찍는 것입니다. 

만약 resolve("this is true") 처럼 resolve 값으로 string이 오게 된다면, 

가장 마지막 console.log는 this is true로 찍혔을 것입니다.

 

이번에는 두 단계의 promise를 처리해보도록 하겠습니다.

let myPromise2 = (param) => {
    return new Promise((resolve, reject) => {
    	window.setTimeout(() => {
      	    if (param) {
      	    	console.log("myPromise2 done with RESOLVE status");
      		    resolve("myPromise2 resolve");
      	    }
      	    else {
      	    	console.log("myPromise2 done with REJECT status");
      		    reject("myPromise2 reject");
      	    }
        }, 3000);
    });
}

let myPromise = (param) => {
    return new Promise((resolve, reject) => {
        window.setTimeout(() => {
      	    if (param) {
      	    	console.log("myPromise done with RESOLVE status");
      		    resolve(true);
      	    }
      	    else {
      	    	console.log("myPromise done with REJECT status");
      		    reject(false);
      	    }
        }, 3000);
    });
}

let promiseTwoDepthExample = () => {
	console.log(">>>> start promiseTwoDepthExample");

	let p = myPromise(true)
	        .then((result) => myPromise2(result))
	        .then((result) => console.log(result), (err) => console.log(err));
    console.log("<<<< end promiseTwoDepthExample");
}

promiseTwoDepthExample();
>>>> start promiseTwoDepthExample
<<<< end promiseTwoDepthExample
myPromise done with RESOLVE status
myPromise2 done with RESOLVE status
myPromise2 resolve

콜백으로 처리했었다면 벌써 들여쓰기 파티가 되었겠지만, 

.then

.then

으로 처리해주니 처음 것이 성공하고 나면 다음 것이 순차적으로 실행되겠구나 ~ 하는 힌트를 얻을 수 있습니다.

 

그런데 .then 안에는 인자가 하나일 수도 있고 두개일 수도 있다는 것을 발견하셨을 겁니다.

.then에 첫번째 인자는 resolve를 만났을 때 실행되는 부분입니다.

.then의 두 번째 인자는 reject를 만났을 때 실행되는 부분입니다.

 

그럼 만약에 myPromise(true) 대신에 myPromise(false)로 바꾸어본다면 output에는 어떻게 찍히게 될까요 ??

이 경우에는 조금 특이하게 동작합니다.

promise에서 만약 reject status가 발생하게 되면 .then().then().then()을 끝까지 순회하며 reject status를 처리하는 부분을 찾습니다.

즉, 첫 번째 then에는 reject handler가 없지만, 

두 번째 then에는 console.log(err)이라는 reject handler가 있기때문에, false라는 로그도 찍히게 되는 것입니다.

>>>> start promiseTwoDepthExample
<<<< end promiseTwoDepthExample
myPromise done with REJECT status
false

만약 then의 끝까지 봤는데, reject handler가 없다면, console에는 "Uncaught (in promise) false" 에러가 발생합니다.

(.catch로 then의 reject handling을 하는 방법도 있지만, 여기에서는 다루지 않도록 하겠습니다.)

 

 

promise다음에 나오는 코드들은 promise의 실행 여부에 상관 없이 실행되는 것도 여전히 확인할 수 있습니다.

"<<<< end promiseTwoDepthExample" 이 두번째로 실행되는 것을 보면 말이죠.

 

 

다시 첫 번째 promise쓰임으로 돌아가 보도록 하겠습니다. 

만약 promise 이후에 실행되는 코드 라인이 promise가 끝난 후에 실행되어야 하는 것이라면, promise가 끝날 때까지 기await을 이용해 promise가 끝날 때까지 기다릴 수 있습니다. 

 

이 경우에는 어찌 보면 flow를 제어해 sync하게 흐름을 맞춘다고 생각하시면 될 것 같습니다. 

let myPromise = (param) => {
    return new Promise((resolve, reject) => {
        window.setTimeout(() => {
      	    if (param) {
      	    	console.log("myPromise done with RESOLVE status");
      		    resolve(true);
      	    }
      	    else {
      	    	console.log("myPromise done with REJECT status");
      		    reject(false);
      	    }
        }, 3000);
    });
}

let awaitExample = async () => {
	console.log(">>>> start awaitExample");

	let p = await myPromise(true)
	        .then((result) => console.log(result), (err) => console.log(err))
    console.log("<<<< end awaitExample");
}

awaitExample();
>>>> start awaitExample
myPromise done with RESOLVE status
true
<<<< end awaitExample

항상 두 번째 라인에 위치했던 "<<<< end xxxxxx" 부분이 이번에는 promise가 끝나기를 얌전히 기다렸다가 

가장 마지막에 출력되는 것을 확인할 수 있습니다. 

 

sync를 맞추는 await 키워드는, async 함수 안에서만 사용할 수 있습니다. 

async 함수를 만드는 법은 간단하게 함수 선언 전에 async 키워드를 붙여주는 것입니다. 

 

글을 마치며 한가지 팁으로, 

promise를 사용하시기 전에 function의 플로우를 다이어그램으로 그려보시면, 도움이 많이 되실겁니다 !

Comments