본문 바로가기
Language/JavaScript

[JavaScript] 비동기 처리와 Callback, Promise, Async/Await

by J4J 2021. 6. 29.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 비동기와 callback, promise, async/await에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

비동기란?

 

비동기라고 하는 것은 간단하게 말씀드리면 특정 기능이 수행될 때 해당 기능이 모두 완료될 때까지 기다리지 않고 다른 기능들을 수행하는 것을 의미합니다.

 

대표적으로 사용되는 곳이 서버쪽과 데이터를 주고받을 때 사용되는데 클라이언트에서 서버 쪽에 비동기 요청을 해놓고 클라이언트에서는 다른 작업을 수행하고 있다가 서버 쪽에서 모든 작업이 완료되어 response를 해주면 그 순간 전달받은 데이터를 클라이언트에서 사용하는 방식입니다.

 

 

비동기처리 구조

 

 

장/단점

 

비동기처리를 사용했을 때의 장점은 작업 처리 속도가 빨라진다는 것입니다.

 

서버에 작업 처리를 요청해두고 클라이언트에서는 자신이 처리할 다른 작업들을 동시에 처리하기 때문에 더 빠른 속도로 작업 처리를 할 수 있게 됩니다.

 

 

비동기 처리를 사용했을 때의 단점은 서버 부하가 발생할 수 있다는 것입니다.

 

비동기 처리를 서버에 요청하고 클라이언트에서는 다음 처리를 기다리지 않기 때문에 잘못된 로직이 수행되면 비동기 요청을 기하급수적으로 한 번에 서버에 전달할 수도 있습니다.

 

그럴 경우 서버쪽에서는 너무 많은 양의 request가 전달되었기 때문에 부하가 발생될 수 있습니다.

 

 

비동기 처리 방법

 

자바스크립트에서 비동기 처리를 하는 방법은 크게 3가지가 있습니다.

 

  • Callback
  • Promise
  • Async/Await

 

 

반응형

 

 

CallBack

 

callback은 ES6버전 이전에 사용되던 비동기 처리 방법입니다.

 

간단한 예제코드를 보여드리면 다음과 같습니다.

 

<script>
    function addCallback(val, asyncFunction) {
        setTimeout(() => asyncFunction(val*2, null), 1000);
    }

    addCallback(10, (res, error) => {
        if(error) {
            console.log(error);
            return;
        }

        console.log(`first: ${res}`); // first: 20
        addCallback(res, (res, error) => {
            if(error) {
                console.log(error);
                return;
            }

            console.log(`second: ${res}`); // second: 40
            addCallback(res, (res, error) => {
                if(error) {
                    console.log(error);
                    return;
                }

                console.log(`third: ${res}`); // third: 80
            })
        })
    })
</script>

 

 

위의 코드는 10이라는 값을 집어넣었을 때 1초마다 값이 x2가 되는 코드입니다.

 

그리고 위의 코드를 보면 callback의 문제점이 명확히 확인이 됩니다.

 

callback을 이용하여 전달받은 결과값을 그대로 다시 비동기 요청을 할 때 사용하기 위해서는 callback 내부에 다시 callback함수를 사용해야 됩니다.

 

그리고 사용되는 callback의 갯수가 많아지면 내부에 존재하는 callback의 깊이가 깊어지게 되는데 이러한 현상이 발생되는 것을 callback hell (콜백 지옥)이라고 부릅니다.

 

callback hell이 발생되면 코드가 간결하지 못하기 때문에 가독성이 떨어지게 되어 유지보수하기가 힘들어집니다.

 

 

 

callback이 실제로 사용되는 대표적인 케이스는 jquery의 ajax입니다.

 

간단한 예제코드를 보여드리면 다음과 같습니다.

 

$("#id").on("click", function() {
	$.ajax({
		url: "input url_1",
		type: "post",
        	data: data,
		success: function(res_1) {
                    $.ajax({
                          url: "input url_2",
                          type: "post",
                          data: res_1,
                          success: function(res_2) {
                              alert(res_2);
                          },
                          error: function(error) {
                              alert(error);
                          }
                    })
		},
		error: function(error) {
			alert(error);
		}
	})
})

 

 

위의 코드에서도 callback의 내부에 계속해서 작성하다 보면 callback hell이 발생된다는 것을 느끼실 수 있을 것입니다.

 

그리고 그에 따라서 코드가 간결하지 못하기 때문에 유지보수가 힘들어지게 될 것입니다.

 

 

Promise

 

promise는 callback의 단점을 보완하고자 ES6버전에 등장한 비동기 처리 방법입니다.

 

promise도 callback의 처음 예제와 동일하게 실행되는 예제코드를 작성하면 다음과 같습니다.

 

<script>
    function addPromse(val) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(val*2);
            }, 1000);
        })
    }

    addPromse(10).then((res) => {
        console.log(`first: ${res}`); // first: 20
        return addPromse(res);
    }).then((res) => {
        console.log(`second: ${res}`); // second: 40
        return addPromse(res);
    }).then((res) => {
        console.log(`third: ${res}`); // third: 80
    }).catch((error) => {
        console.log(error);
    })
</script>

 

 

callback의 예제코드와 비교해봤을 때 확실히 코드가 간결해지는 느낌을 받을 수 있을 것입니다.

 

하지만 편하게 사용될 것처럼 보이는 promise도 단점을 가지고 있습니다.

 

위의 코드에서 만약 어딘가에서 에러가 발생되었다고 가정했을 때 어디서 발생된 에러인지를 명확히 확인할 수 없게 됩니다.

 

왜냐하면 모든 에러처리는 마지막에 있는 .catch구문에서 처리가 되기 때문이죠.

 

 

728x90

 

 

promise가 실제로 사용되는 대표적인 케이스는 react, vue와 같은 곳에서 사용되는 axios입니다.

 

이 예제 코드도 callback에서 작성한 것과 동일하게 사용되도록 해보겠습니다.

 

axios.post('input url_1', data)
     .then((res_1) => {
         return axios.post('input url_2', res_1);
     })
     .then((res_2) => {
         alert(res_2);
     })
     .catch((error) => {
         alert(error);
     })

 

 

callback과 비교했을 때 코드가 매우 간결해진것을 확인할 수 있습니다.

 

하지만 위에서 언급한것과 마찬가지로 에러가 발생되면 어디서 발생되었는지 명확히 확인이 불가합니다.

 

 

Async/Await

 

async/await은 promise의 단점을 보완하고자 ES8버전에 등장한 비동기 처리 방법입니다.

 

async/await도 callback, promise와 마찬가지로 예제 코드를 작성하면 다음과 같습니다.

 

</script>
    async function getAsyncAwait() {
        let res1;
        let res2;
        let res3;
        
        try {
            res1 = await addPromse(10);
            console.log(`first: ${res1}`); // first: 20
        } catch(error) {
            console.log(error);
        }

        try {
            res2 = await addPromse(res1);
            console.log(`second: ${res2}`); // second: 40
        } catch(error) {
            console.log(error)
        }

        try {
            res3 = await addPromse(res2); 
            console.log(`third: ${res3}`); // third: 80
        } catch(error) {
            console.log(error);
        }
    }

    getAsyncAwait();
</script>

 

 

코드를 보면 비동기처리라고 부르기 어색하게 익숙한 일반적인 코드 구조로 되어있고 또한 try...catch문을 사용하여 예외처리도 할 수 있습니다.

 

promise에서 예외가 발생되면 어디서 발생되었는지 확인할 수 없던 단점들을 async/await을 이용하여 해결하게 된 것입니다.

 

 

 

async/await을 사용할 때는 async와 await을 어떤 상황에서 사용하는지를 이해하셔야 합니다.

 

우선 await에 대해 먼저 말씀드리면 await은 비동기처리가 수행되는 함수를 리턴 받아 변수에 저장할 때 사용합니다.

 

await의 위치는 실행되는 함수의 왼쪽에 작성해주시면 됩니다.

 

만약 비동기처리된 response를 리턴 받고 싶지 않으면 await은 사용하지 않으셔도 됩니다.

 

 

// 가능
const res1 = await addPromise(10);

// 가능
addPromise(10);

// 불가능
const res1 = addPromise(10);

 

 

async의 사용 방법은 매우 간단합니다.

 

함수 내부에 await이 사용된다면 해당 함수를 정의할 때 function 왼쪽에 async를 붙여주시면 됩니다.

 

// 가능
async function getAsyncAwait() { ... }

// 가능
const getAsyncAwait = async function() { ... }

// 가능
const getAsyncAwait = async () => { ... }

 

 

 

async/await도 실제 사용되는 대표적인 케이스는 promise와 동일하게 axios입니다.

 

이 예제 코드도 callback, promise와 동일하게 작성하면 다음과 같습니다.

 

let res_1;
let res_2;

try {
    res_1 = await axios.post('input url_1', data);
} catch(error) {
    alert(error);
}

try {
    res_2 = await axios.post('input url_2', res_1);
} catch(error) {
    alert(error);
}

alert(res_2);

 

 

async/await을 사용하면 코드도 간결하며 예외 처리도 할 수 있게 되어서 만약 한 가지 사용을 해야 되면 async/await 사용을 추천드립니다.

 

 

 

 

 

이상으로 비동기와 callback, promise, async/await에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

728x90
반응형

'Language > JavaScript' 카테고리의 다른 글

[JavaScript] 클로저 (Closure)  (0) 2021.06.22
[JavaScript] This와 Call, Apply, Bind  (0) 2021.06.21
[JavaScript] Lexical Scope  (0) 2021.06.20
[JavaScript] 실행 문맥 (Execution Context)  (0) 2021.06.20
[JavaScript] == vs ===  (0) 2021.06.16

댓글