본문 바로가기

Front End/Javascript

[JavaScript] Synchronous(동기)/비동기(Asynchronous)


프로그램을 실행할 때 항상 한 작업을 끝낸 후 다음 작업을 시작하는 것을 동기(Synchronous) 프로그램이라고 하고, 그에 비해 어떤 작업을 처리하는 도중 다른 작업을 시작할 수 있으면 비동기(Asynchronous) 프로그램이라고 합니다.

동기적 처리 방식의 장점은 동기적, 순차적으로 즉시 실행되어 순서를 보장할 수 있다는것 입니다. 하지만 단점도 존재하는데  현재 작업이 종료될 때 까지 다음 작업이  실행이 되지 않는 blocking 현상이 발생하여 프로그램이 멈춰진것 처럼 보일 수 있습니다. blocking 현상이란 현재의 작업이 완료될 때 까지 작업이 중단되고 이후 코드들이 실행되지 않는현상입니다.

const task1 = () => {
            // 작업시간이 5분 정도 소요된다고 가정합니다.
}

const task2 = ()  => {
            // 작업시간이 1분 정도 소요된다고 가정합니다.
}

task1();
task2();  //  이 함수는 task1() 이 종료될떄까지 작동하지 않습니다. 5분동안은 멈춰진것 처럼 보이게 됩니다.

 

 

자바스크립트는 싱글 스레드 언어이기 때문에 한 번에 하나의 작업만 수행할 수 있습니다. 즉, 이전 작업이 완료되어야 다음 작업을 수행할 수 있게 된다는 말입니다.  프로그래밍을 하면서 일반적으로 각 함수와 코드들이 위에서 아래로 차례로 동작하는 방식이라고 할 수 있습니다. 이러한 코드 순차 실행을 동기(Synchronous) 라고 부릅니다.

 

Thread란 프로세스내의 실행흐름을 이야기하는것으로 실행흐름이 1개이면 싱글스레드( Single Thread) 작업흐름이 여러개이면 멀티 스레드(multi Sthread)라고 합니다. 대부분 싱글스레드 이지만 동영상을 다운로드와 동시에 재생이된다거나 채팅(보내기/받기 동시 실행)같은 경우는 멀티스레드입니다.

 

동기식 처리

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Synchronous(동기)</title>
    <script>
        window.onload = ()=>{
            console.log(`첫번째 실행 ----- 1`);
            console.log(`두번째 실행 ----- 2`);
            console.log(`세번째 실행 ----- 3`);
        }
    </script>    
</head>
<body>
</body>
</html>

 

 

자바스크립트로 여러 작업을 동시에 처리하기 위해 비동기(Asynchronous) 라는 개념을 도입하였습니다. 특정 작업의 완료를 기다리지 않고 다른 작업을 동시에 수행할 수 있도록 하였다는 것 입니다.

 

자바스크립트를 실행하는 콜 스택(Call Stack)은 싱글 스레드지만, 서버에게 리소스를 요청하거나 파일 입출력 혹은 타이머 대기 작업을 실행하는 Web APIs 들은 멀티 스레드이기 때문에 동시 작업 처리가 가능합니다. WEB API 에는 DOM, AJAX, settime같은것들 이 있습니다.  이런 함수들이  비동기로 동작하는 대표적인 함수들 입니다. 

 

비 동기식 처리

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>비동기(Asynchronous)</title>
    <script>
        window.onload = () => {
            console.log(`첫번째 실행 ----- 1`);
            setTimeout(() => {
                console.log(`두번째 실행 ----- 2`);
            }, 1000);
            console.log(`세번째 실행 ----- 3`);
        }
    </script>
</head>

<body>
</body>

</html>

 

 

비동기적으로 실행되면 원하는 결과가 아닌 엉뚱한 결과를 맞이할 수 있습니다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>비동기(Asynchronous)</title>
    <script>
        function taskA(a, b) {
            return setTimeout(() => {
                const res = a + b;
                return res;
            }, 2000);
        }
        window.onload = () => {
            //비동기실행
            const res = taskA(10, 20);
            console.log(res);  
        }
    </script>
</head>
<body>
</body>
</html>

 

작업시간이 아주 오래 걸리는 작업이 있다고 가정해 보도록 하겠습니다.

taskA 함수는 비동기 함수인 setTimeout을 이용하여 2초 후에 결과를 리턴하도록 했습니다. 

위의 내용을  실행 했을때 결과는 30이 아니라 2가 나옵니다.

왜 이런 결과가 나왔을 까요? taskA()함수가 응답하기 전에 res 변수값을 출력했기 때문입니다.

 

위와 같은 문제를 해결하는 방법으로 여러가지가 있는데 전통적으로 callback 함수를 이용해서 처리를 해 왔습니다.

마지막 매개변수로 비동기 작업이 완료되고 처리할 내용을 함수로 전달해 주면 됩니다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>비동기(Asynchronous)</title>
    <script>
        function taskB(a, b, callback) {
            // 2. 비동기 작업으로 setTimeout 내부 코드가 2초 뒤에 실행된다. (Non-Blocking)
            setTimeout(() => {
                // 4. 비동기 작업을 수행한다.
                const res = a + b;
                // 5. 비동기 작업이 완료되면 콜백 함수를 호출해 연산의 결과값을 인수로 전달한다.
                callback(res);
             }, 2000);
        }

        window.onload = () => {
            // 비동기 실행을 차례대로 실행하게 하기위해 callback함수를 이용한다.
            // 1. taskB 함수를 호출한다. 이때 인수로 화살표 함수로 만든 콜백 함수를 전달한다.
            taskB(10, 20, (response) => {
                // 6. taskB의 비동기 작업이 종료되고, 콜백 함수가 호출되어 실행된다.
                console.log("tack B 결과 : ", response); // 30
            });
            // 3. taskB 내부 함수가 비동기 작업이므로 해당 작업의 종료를 기다리지 않고 먼저 출력된다.
            console.log("tack B 결과!!!!");
        }
    </script>
</head>
<body>
</body>
</html>

 

 

이제 올바른 결과가 나왔습니다.

 

콜백 지옥(Callback hell)이라는 말은 한번쯤 들어 보셨을텐데 비 동기작업을 처리하는 함수가 여러개 있을경우 차례대로 실행을 해야 한다면   콜백 지옥(Callback hell)을 맛보게 됩니다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>비동기(Asynchronous)</title>
    <script>
        function first(callback) {
            console.log("1번째 실행");
            callback();
        }

        function second(callback) {
            setTimeout(() => {
                console.log("2번째 실행");
                callback();
            }, 1000);
        }

        function third(callback) {
            setTimeout(() => {
                console.log("3번째 실행");
                callback();
            }, 1000);
        }

        function fourth(callback) {
            setTimeout(() => {
                console.log("4번째 실행");
                callback();
            }, 1000);
        }

        function fifth(callback) {
            setTimeout(() => {
                console.log("5번째 실행");
                callback();
            }, 1000);
        }

        function sixth(callback) {
            setTimeout(() => {
                console.log("6번째 실행");
                callback();
            }, 1000);
        }

        window.onload = () => {
            // 첫번째 함수끝나면 두번째 함수 실행 두번째 함수 끝나면 세번째 함수 실행 ....
            // 콜백 지옥(Callback hell)에 빠진다.
            first(function () {
                second(function () {
                    third(function () {
                        fourth(function () {
                            fifth(function () {
                                sixth(function () {
                                    setTimeout(() => {
                                        console.log("작업 종료!!!");
                                    }, 1000);
                                });
                            });
                        });
                    });
                });
            });
        }

       
    </script>
</head>
<body>
</body>
</html>

 


콜백 함수는 하나만 썼을 때는 간단하지만, 비동기로 함수의 매개 변수에 다른 콜백 함수가 중첩되어 사용된다면 그 코드를 보기에 굉장히 어렵고 유지보수도 힘들어 지게 됩니다.

 

첫번째 함수끝나면 두번째 함수 실행 두번째 함수 끝나면 세번째 함수 실행 이렇게 처리하고 싶은 경우 콜백함수를 이용하면 콜백 지옥(Callback hell)에 빠지게 됩니다.

 

 

위 코드 문제 점인 콜백 함수의 error, success의 처리를 보다 간단하게 하기 위해  Promise가 생기게 되었습니다.

콜백 함수는 비동기를 순차적으로 처리하기 위한 일종의 잔머리인 편법 같은 것이지 정식으로 지원하는 비동기 전용 함수가 아닙니다. 자바스크립트에서 비동기 함수를 처리하기 위한 Promise 객체는 이러한 한계점을 극복하기 위해 비동기 처리를 위한 전용 객체로서 만들어진것 입니다.

 

Promise는 비동기 작업의 성공 또는 실패와 그 결과값을 나타내는 객체입니다. Promise를 사용하면 비동기 작업을 쉽고 깔끔하게 연결할 수 있습니다.

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>콜백 지옥(callback hell)</title>
    <script>
        const promiseDivision = (a, b) => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    if(b!==0){
                        result = a / b;
                        resolve(`${a} / ${b} = ${result}`);
                    }else{
                        reject('0으로 나눌수 없습니다.');
                    }
                }, 1000);
            });
        };

        console.log("비동기 함수의 호출 : " + promiseDivision(1, 1));

        promiseDivision(20, 4)
            .then((result) => {
                console.log(result);
            })
            .catch((err) => {
                console.log(err); // Error!!
            })
            .finally(() => {
                console.log('계산완료됨!!!');
            });


        promiseDivision(11, 0)
            .then((result) => {
                console.log(result);
            })
            .catch((err) => {
                console.log(err); // Error!!
            })
            .finally(() => {
                console.log('계산완료됨!!!');
            });
    </script>
</head>

<body>
</body>

</html>

 

비 동기식 함수의 호출은 암시적으로 Promise 객체를 반환합니다.

 console.log("비동기 함수의 호출 : " + promiseDivision(1, 1)); // 비동기 함수의 호출 : [object Promise]

 

Promise 객체를 사용할때는 다음과 같이 처리 합니다.

 [object Promise]
.then((result)=>{
      // 성공시 처리할 코드 result에 처리 결과가 넘어옵니다.
})
.catch((error)=>{
      // 실패시 처리할 코드 error에 error 객체가 넘어옵니다.
})
.finally(() => {
      // 성공/실패에 상관없이  항상 실행할 내용을 처리합니다.
 })

 

비동기로 처리되는 함수 처리에 promise를 사용하면 한결 코드가 간결해지고 읽기 편해집니다.

다음은 Promise에 대하여 알아 보도록 하겠습니다.