JavaScript/Nodejs

[Node.js] 콜백지옥 벗어나기 promise

반응형

  Nodejs의 비동기처리?

Nodejs는 Non-Blocking 방식으로 서버에 요청을 보냇을 때 응답이 올때까지 기다려주지 않습니다.

요청을 보내고 바로 다른일을 하다가 응답이 오면 해당 값을 사용할 수 있게 되는것이죠.

아래 비동기의 예시를 들어보겠습니다.

 

예시

console.log("1");

setTimeout(() => {
    console.log("timeout");
} , 100);

console.log("2");

위 코드처럼 setTimeout을 넣어서 예제코드를 작성해 보았습니다.

우리가 알고있는 방식대로면

1
timout
2

이렇게 출력이 되겠지만

1
2
timeout

이렇게 출력이 됩니다.

 

 

  비동기처리의 대표적인 문제사례

이 비동기 처리의 문제점에대해 대표적 예시로는 Jquery의 ajax통신이 있습니다.

const getData = () => {
    let testResult;
    $.get('http://example.com/', (res) => {
        testResult = res;
    })
    return testResult;
}

console.log(getData());

http://example.com/ 에 요청을 보내면 "Javascript Callback" 이라는 response 를 준다고 가정을 해 봅시다.

 

해당 코드는  Javascript Callback이라는 문자를 log를 찍어내지 않고 undefined를 찍어내게됩니다.

왜냐하면, 먼저 url에 요청을 보낸 후 응답을 기다리지 않고 바로 log 를 찍어내기 때문입니다.

 

이럴땐 따로 비동기적 처리를 해주어야 합니다.

어떻게 해야하는지는 이제부터 알아보도록 하죠.

 

 

 

  CallBack을 이용한 비동기 처리

가장 먼저 나왔던 해결법이 바로 CallBack을 이용하는것 입니다.

const getStrnig = (callback) => {
	callback("callbackTest");
}

getString = (str => {
	console.log("callbackTest");
})

Callback은 먼저 이런형식 입니다.

이 코드를 그래서 어떻게 쓰라는건데?

 

위의 문제의 예제를 적용해보겠습니다.

const getData = (callback) => {
    $.get('http://example.com/', (res) => {
        callback(res);
    })
}

console.log(getData());

이런식으로 작성을 해주시면 "Javascript Callback" 이라는 문자열이 콘솔에 아주 잘 찍히게 됩니다.

 

콜백지옥

하지만, Callback이 정말 만능은 아닙니다.

Callback은 자칫하면 콜백지옥 이라는것에 빠질 수 있는데요

 

예시로 setTimeout을 이용하여 커피를 만드는 순서를 나타내 보겠습니다.

let stack = '';

setTimeout((order)=>{
    console.log(stack += order);
    setTimeout((order)=>{
        console.log(stack += " " + order);
        setTimeout((order)=>{
            console.log(stack += " " + order);
        }, 300, '얼음');
    }, 300, '물');
},300,'에스프레소');
에스프레소
에스프레소
에스프레소 물 얼음

이런식으로 출력이 됩니다.

그런데 이렇게 하게되면 setTimeout이 너무 중첩되어 보기가 힘들고 가독성이 떨어집니다.

이런형태를 보고 바로 콜백지옥 이라고 합니다.

 

이 콜백지옥을 해결하는 방법은 3가지로 나눌 수 있습니다.

 

기명함수, Promise, async

 

  기명함수

위 콜백지옥에서 나온 코드를 기명함수로 아래와같이 나타낼 수 있습니다.

let stack = '';

const firstStep = (order) => {
    console.log(stack += order);
    setTimeout(secondStep , 300 , '물')
}

const secondStep = (order) => {
    console.log(stack += " " + order);
    setTimeout(thirdStep , 300 , '얼음')
}
const thirdStep = (order) =>{
    console.log(stack += " " + order);
}

setTimeout(firstStep, 300 , "에스프레소")

이런식으로 나타낼 수 있습니다.

가독성은 좋지만, 단점으로는 일회성 함수를 계속해서 변수로 명시를 해주어 사용하는것이 굉장히 비효율적 입니다.

 

이 부분을 대체하기 위하여 Promise와 async가 있습니다.

  Promise

new Promise( (resolve) => {
    setTimeout(()=>{
        let stack = '에스프레소'
        console.log(stack)
        resolve(stack);
    },300);
}).then((prevStack) =>{
    return new Promise((resolve) => {
        setTimeout(()=>{
            console.log(prevStack += " " + "물");
            resolve(prevStack);
        },300);
    });
}).then((prevStack)=>{
    return new Promise((resolve) => {
        setTimeout(()=>{
            console.log(prevStack += " " + "얼음");
            resolve(prevStack);
        },300);
    });
})

Promise를 사용한 코드로 위에서 아래로 진행을 하는 코드이며

이런형태로 작성을 하게 된다면 동기처럼 보이게 하는 코드가 완성이 됩니다.

하지만 더 간단하게 작성하는 방법이 있습니다.

 

const addStack = (order) =>{
    return (prevStack) => {
        return new Promise((resolve) => {
            setTimeout(()=>{
                const stack = prevStack? `${prevStack} ${order}` : order;
                console.log(stack);
                resolve(stack);
            },300);
        });
    };
};

addStack('에스프레소')()
    .then(addStack('물'))
    .then(addStack('얼음'));

이런식으로 더 짧게 작성을 할 수 있습니다.

잘 보시면 반복되는 부분이 보이는데 해당 부분을 함수화하여 고차함수로 간단하게 만드는것 입니다.

addStack이 끝나지 않아 계속해서 prevStack에는 전에 저장되었던 문자가 저장이 되어있어 중첩이 가능한 것 입니다.

 

글이 너무 길어져 async await과 Promise에대한 자세한 글과 다음에 작성하겠습니다.

반응형