새소식

SW 개발

[NodeJS] 자바스크립트 비동기 연산을 다루는 Promise

  • -

 

자바스크립트에서 비동기 연산을 다루는 Promise,

Promise는 콜백지옥에서 탈출하게 해주는 자바스크립트 API이다.

 

 

 

콜백지옥(Callback Hell) 이란?

자바스크립트의 비동기 프로그래밍시 발생하는 문제로 함수의 매개변수로 넘겨지는 콜백 함수가 반복되어 가독성도 떨어지고 로직을 변경하기 어려워지는 경우가 있다. 요즘에는 promise와 async를 사용하면서 콜백지옥을 탈출하기 쉬워졌다.

 

step1(function (value1) {
    step2(function (value2) {
        step3(function (value3) {
            step4(function (value4) {
                step5(function (value5) {
                    step6(function (value6) {
                        // Do something with value6
                    });
                });
            });
        });
    });
});

콜백 지옥 예시

 

 


 

 

자바스크립트에서 기본적이고 가장 많이 쓰이는 패턴은

ajax('http://a.com/book_info', (result) => {
    // some function
});

ajax통신으로 a.com에서 book_info를 가져와, 콜백으로 result를 받게 되는 이와 같은 방식인데요.

 

 

 

function delay(sec, callback) {
    setTimeout(() => {
        callback(new Date().toISOString());
    }, sec * 1000);
};

console.log('delay가 호출된 시간', new Date().toISOString());

delay(1, (result) => {
    console.log('delay가 처리된 시간', result);
})

console.log('비동기인지확인')

// sec : 몇 초 뒤에 실행할지 (실행에 필요한 옵션)
// callback : 결과를 받아 처리하는 애 (콜백함수)

delay라는 비동기 처리 함수를 만들어 Promise를 어떻게 사용하는지 살펴보도록 하겠습니다.

 

 

결과

7번째 라인이 처리된 후, delay가 처리되어 결과가 표시됩니다. 정확히 1초 뒤에 delay가 처리된 결과가 표시돼요.

비동기인지 확인하기 위해 console.log('비동기인지확인')부분을 추가했는데, 만약 이 코드가 동기적으로 처리된다면 결과가

delay가 호출된 시간 2020-08-26T06:43:31.462Z
delay가 처리된 시간 2020-08-26T06:43:32.466Z
비동기인지확인

이처럼 되겠죠.

 

비동기적으로 처리되기 때문에 

console.log('delay가 호출된 시간', new Date().toISOString()); 가 실행되고,

delay(1, (result) => {

    console.log('delay가 처리된 시간', result);

}) 이 부분은 큐에 들어가고,

console.log('비동기인지확인') 이 처리되고,

마지막으로 큐에 들어간 delay가 처리됩니다.

 

동기와 비동기가 아직 헷갈린다면, 이전 포스팅을 참고해주세요.

 

 

 

 

function delay(sec, callback) {
    setTimeout(() => {
        callback(new Date().toISOString());
    }, sec * 1000);
};

delay(1, (result) => {
    console.log(1, result);
});

delay(1, (result) => {
    console.log(2, result);
});

delay(1, (result) => {
    console.log(3, result);
});

이 코드의 결과는 어떻게 될까요?

 

 

동기적이라면,

1 2020-08-26T06:48:52.787Z
2 2020-08-26T06:48:53.792Z
3 2020-08-26T06:48:54.792Z

이렇게 되겠지만,

 

 

비동기이기 때문에

결과

결과는 이와 같이 됩니다.

 

 

 

 

만약 동기적 처리처럼 결과를 내고 싶으면,

function delay(sec, callback) {
    setTimeout(() => {
        callback(new Date().toISOString());
    }, sec * 1000);
};

delay(1, (result) => {
    console.log(1, result);

    delay(1, (result) => {
        console.log(2, result);

        delay(1, (result) => {
            console.log(3, result);
        });
        
    });

});

이와 같이 코드를 수정해야 합니다.

 

결과

 

 

 

function delay(sec, callback) {
    setTimeout(() => {
        callback(new Date().toISOString());
    }, sec * 1000);
};

delay(1, (result) => {
    console.log(1, result);

    delay(1, (result) => {

        delay(1, (result) => {
            console.log(3, result);
        });    

        console.log(2, result);
    });

});

이 코드의 결과는, 우리가 눈으로 밑으로 쭉 보기엔 결과가 1, 3, 2로 찍힐 것 같지만 비동기적으로 동작하기 때문에

 

 

결과

결과는 동일합니다.

 

 

우리가 코드를 볼 때, 위에서 아래로 보기 때문에 결과가 어떻게 될지 로직을 바꿀 때 어떻게 될 지 확인하기 어려워집니다. 이때 필요한 것이 Promis, asyn, await

 

 

 


 

delayP(1).then((result) => {
    console.log(1, result);
})

Promise를 사용하여 함수를 호출하는 방식은 이렇습니다.

 

함수(실행에필요한옵션(인자)).then(결과를처리할콜백)

 

delayP라는 함수를 호출 했을 때 리턴되는 그 값은, then이라는 메소드를 가지고 있고 then이라는 메소드를 가지고 있는 것이 Promise의 인스턴스 입니다. 즉, delayP에서 만드는 것이 바로 Promise의 인스턴스 입니다.

 

 

 

function delayP(sec) {
    // promise의 인스턴스를 리턴하고
    // then에서 리턴한 것을 받는다.
    return new Promise((resolve, reject) => {
        // Promise 생성시 넘기는 callback = resolve, reject
        // resolve 동작 완료시 호출, 에러 났을 경우 reject
        setTimeout(() => {
            resolve(new Date().toISOString());
        }, sec*1000);
    });
}

delayP(1).then((result) => {
    console.log(1, result);
})

이제 callback 대신 resolve를 호출하게 합니다. 이제 Promise를 이용해 콜백안의 콜백처럼 동기적 처리를 하는 코드를 만들어보겠습니다.

 

 

 

 

delayP(1).then((result) => {
    console.log(1, result);
    return delayP(1);
}).then((result) => {
    console.log(2, result);
})

Promise패턴에서는 콜백안에 콜백을 구현해 동기적으로 처리하는 것이 아니라, 콜백 함수를 순차적으로 지정합니다.

 

 

결과

1~3번째까지, Promise의 resolve를 통해 결과를 받고, 그 결과를 콘솔에 찍고 다시 delayP함수를 호출한 결과를 두번째 콜백함수로 넘겨줍니다.

 

 

 

delayP(1).then((result) => {
    console.log(1, result);
    return delayP(1);
}).then((result) => {
    console.log(2, result);
    return delayP(1)
}).then((result) => {
    console.log(3, result);
})

결과

 

 

 

 

delayP(1).then((result) => {
    console.log(1, result);
    return delayP(1);
}).then((result) => {
    console.log(2, result);
    return delayP(1)
}).then((result) => {
    console.log(3, result);
}).then((result) => {
    console.log(4, result);
})

3번째 실행 코드에서 아무것도 리턴하지 않고 4번째 콜백함수를 붙여주면 어떨까요?

 

 

 

아무것도 리턴하지 않아도 then의 리턴값은 Promise이고, 아무것도 리턴하지 않았다면 resolve가된 Promise를 리턴하게 되서 바로 실행되고 우리가 resolve를하지 않았기 때문에 undefined가 표시됩니다.

 

 

 

delayP(1).then((result) => {
    console.log(1, result);
    return delayP(1);
}).then((result) => {
    console.log(2, result);
    return delayP(1)
}).then((result) => {
    console.log(3, result);
    return 'hello'
}).then((result) => {
    console.log(4, result);
})

세번째 실행코드에서 'hello'라는 스트링을 리턴하고, 네번째 실행코드를 연결하면

 

 

네번째 실행코드에서는 'hello'를 result로 받게됩니다.

 

 

 

다음 포스팅은 Promise처럼 비동기연산을 다루는 async / await 에 대해 알아보도록 하겠습니다.

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.