2차 공부/TIL

24.07.24 콜백지옥 / 동기, 비동기

공대탈출 2024. 7. 24. 17:36

1. 콜백지옥

function do () {
	$.get(url, function (result) {
    	setTimeout(function () {
        	startAsyncProcess(function (){
            	...
            }
        }, 1000)
    }
}

연속된 이벤트나 서버통신같은 비동기적 작업을 수행할 때 이러한 콜백함수 지옥에 빠지곤한다.

이렇게 연속된 콜백함수는 코드의 유지보수를 어렵게하며, 다른사람이나 미래의 내가 코드를 봤을 때 이해하기 어렵게 만든다.

 

2. 동기 vs 비동기

동기 (Sync) - 카페에서 줄을서서 주문을 하는데, 주문을 하고 커피가 손님에게 전달될때까지 다음 주문으로 넘어가지 않는다.

 

비동기 (Async) - 카페에서 줄을 서서 주문을 하는데, 주문을 하면 진동벨을 받고 자리에 이동하여 다음 주문을 받는다.

 

1. 동기 synchronous

 - 현재 실행중인 코드가 끝나야 다음 코드를 실행하는 방식

 - CPU의 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적 코드이다.

 - 계산이 복잡해서 오래걸리는 코드도 동기적 코드이다.

 

2. 비동기 asyncronous

 - 실행중인 코드의 완료여부와 무관하게 즉시 다음 코드로 넘어가는 방식

 - setTimeout, addEventListener 등

 - 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 모두 비동기적 코드

 

// 비동기적 코드의 이해
setTimeout(() => {
    console.log("여기가 먼저 실행될까?");
}, 1000);

console.log("여기좀 봐주세요!!!");

 

1초를 기다린 뒤 "여기가 먼저 실행될까?"가 먼저 출력되고 "여기좀 봐주세요!!!"가 출력될 것 같지만, setTimeout은 비동기적 코드이기 때문에 "여기좀 봐주세요!!!"가 먼저나오고 약 1초를 기다린 뒤 "여기가 먼저 실행될까?"가 출력된다.

 

setTimeout(
    function (name) {
        var coffeeList = name;
        console.log(coffeeList);

        setTimeout(
            function (name) {
                coffeeList += ", " + name;
                console.log(coffeeList);

                setTimeout(
                    function (name) {
                        coffeeList += ", " + name;
                        console.log(coffeeList);

                        setTimeout(
                            function (name) {
                                coffeeList += ", " + name;
                                console.log(coffeeList);
                            },
                            500,
                            "카페라떼"
                        );
                    },
                    500,
                    "카페모카"
                );
            },
            500,
            "아메리카노"
        );
    },
    500,
    "에스프레소"
);

setTimeout을 이용하여 여러 카페음료를 console.log하는 것이다. 콜백지옥이다.

작동 순서도 헷갈리고 이해하는데에도 시간이 걸린다.

 

var coffeeList = "";

var addEspresso = function (name) {
    coffeeList = name;
    console.log(coffeeList);
    setTimeout(addAmericano, 500, "아메리카노");
};

var addAmericano = function (name) {
    coffeeList += ", " + name;
    console.log(coffeeList);
    setTimeout(addMocha, 500, "카페모카");
};

var addMocha = function (name) {
    coffeeList += ", " + name;
    console.log(coffeeList);
    setTimeout(addLatte, 500, "카페라떼");
};

var addLatte = function (name) {
    coffeeList += ", " + name;
    console.log(coffeeList);
};

setTimeout(addEspresso, 500, "에스프레소");

어떤 커피를 출력하냐에 따라 함수표현식을 만들어 다음 함수를 이전 함수안에 setTimeout으로 넣은 방식이다.

훨씬 깔끔하지만, 거의 비슷한 기능의 함수를 4개를 만들어 사용하여 효율적이지 못하다.

 


비동기 작업 => 순서를 보장하지 못한다.

따라서 비동기 작업을 동기적으로 표현하여 오류를 방지해야한다.

 

Promise는 비동기 처리에 대하여, 처리가 끝나면 알려달라는 '약속'이다.

 - new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 바로 실행된다.

 - 그 내부의 resolve(또는 reject) 함수를 호출하는 구문이 있을 경우 resolve, reject 둘 중 하나가 실행되기 전까지는 then, catch로 넘어가지 않는다.

 - 따라서, 비동기 작업이 완료될 때 비로소 resolve, reject를 호출한다.

 

new Promise(function (resolve) {
    setTimeout(function () {
        var name = "에스프레소";
        console.log(name);
        resolve(name);
    }, 500);
})
    .then(function (prevName) {
        //안에서 새롭게 Promise를 만든다.
        return new Promise(function (resolve) {
            setTimeout(function () {
                var name = prevName + ", 아메리카노";
                console.log(name);
                resolve(name);
            }, 500);
        });
    })
    .then(function (prevName) {
        //안에서 새롭게 Promise를 만든다.
        return new Promise(function (resolve) {
            setTimeout(function () {
                var name = prevName + ", 카페모카";
                console.log(name);
                resolve(name);
            }, 500);
        });
    })
    .then(function (prevName) {
        //안에서 새롭게 Promise를 만든다.
        return new Promise(function (resolve) {
            setTimeout(function () {
                var name = prevName + ", 카페라떼";
                console.log(name);
                resolve(name);
            }, 500);
        });
    });

위에서 카페음료를 출력하는것을 promise로 바꾼 것이다.

 

var addCoffee = function (name) {
    return function (prevName) {
        //안에서 새롭게 Promise를 만든다.
        return new Promise(function (resolve) {
            setTimeout(function () {
                var newName = prevName ? `${prevName}, ${name}` : name;
                console.log(newName);
                resolve(newName);
            }, 500);
        });
    };
};

addCoffee("에스프레소")()
    .then(addCoffee("아메리카노"))
    .then(addCoffee("카페모카"))
    .then(addCoffee("카페라떼"));

중첩함수를 이용하여 처음과 그 이후를 구분했다.

처음 함수가 실행될 때에는 addCoffee('에스프레소)로 return된 function (prevName)을 즉시 실행하여 prevName이 없으므로 name만 newName에 들어가 resolve되고,

그 후로는 addCoffee('아메리카노')가 실행되어 prevName에는 resolve로 전해진 '에스프레소'가 들어있어

newName = '에스프레소, 아메리카노'가 된다. 또 newName을 resolve하는 것으로 반복하여 마지막 addCoffee('카페라떼')까지 실행되면
"에스프레소, 아메리카노, 카페모카, 카페라떼"가 출력된다