자바스크립트

콜백

데이터_박과장 2023. 11. 16. 09:40

비동기 처리와 콜백 함수

콜백에 관하여 검색하면 항상 비동기 처리라는 말과 같이 나오는 것을 볼 수 있다.
그 이유는 바로 콜백함수를 쓰는 이유가 비동기 처리를 위해서 사용하기 때문이다.

1. 비동기처리

자바스크립트의 비동기처리란 특정 코드가 종료되지 않은 상태라 하더라도 대기하지 않고 다음 코드를 실행하는 자바스크립트의 특성 (병렬적 실행)을 의미한다.

자바스크립트에서 비동기처리가 필요한 이유

화면에서 서버로 데이터를 요청했을 때 서버가 언제 그 요청에 대한 응답을 할지도 모르는 상태에서 다른 코드를 실행 안하고 기다릴 수는 없기 때문이다.

 

 

주의:

 

jQuery의 사용: 코드에서 $.get 함수를 사용하고 있는데, 이는 jQuery 라이브러리의 일부이다. 따라서 이 코드를 브라우저 환경에서 실행하려면 jQuery 라이브러리가 페이지에 포함되어 있어야 합니다. 반면, Node.js 환경에서는 기본적으로 jQuery를 사용할 수 없다. Node.js에서 HTTP 요청을 보내려면 http나 https 모듈, 또는 axios, request, node-fetch와 같은 외부 라이브러리를 사용해야 한다.

 

CORS(Cross-Origin Resource Sharing) 정책: 브라우저 환경에서 외부 도메인으로 HTTP 요청을 보낼 때는 CORS 정책에 따라 제한을 받을 수 있다. 요청하는 서버가 해당 요청을 허용하는 적절한 CORS 헤더를 반환하지 않으면, 브라우저는 보안상의 이유로 응답을 차단한다. 반면, Node.js 환경에서는 CORS 정책이 적용되지 않으므로 이러한 제한 없이 요청을 보낼 수 있다.

 

브라우저에서의 실행

브라우저에서 위 코드를 실행하려면, 다음과 같이 HTML 페이지에 jQuery를 포함시키고 해당 스크립트를 추가해야 한다:

 

<!DOCTYPE html>
<html>
<head>
    <title>Test Page</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
    <script>
        // 여기에 JavaScript 코드를 넣습니다.
    </script>
</body>
</html>

 

비동기처리의 예시

1. 제이쿼리의 Ajax

ajax 통신은 제이쿼리로 실제 웹 서비스를 개발할 때 빼놓을 수 없는 중요한 통신이다. 보통의 경우, 화면에 표시할 이미지나 데이터를 서버에서 불러와 표시해야 하는데 이때 ajax 통신을 사용하여 해당 데이터를 서버로부터 가져올 수 있다.

function getData() {
	var tableData;
	$.get('https://jsonplaceholder.typicode.com/posts/1', function (response) {
		tableData = response;
	});
	return tableData;
}

console.log(getData()); // undefined

1.1 실행 과정

1. $.get() : ajax 통신을 하는 부분 (https://domain.com 에다가 HTTP GET 요청을 날려 1번 상품(product) 정보를 요청하는 코드), 지정된 URL에 ‘데이터를 하나 보내주세요’ 라는 요청을 날리는 것과 같다.
2. 그렇게 서버에서 받아온 데이터는 매개변수인 response에 담긴다.
3. tableData = response;로 받아온 데이터를 변수 tableData에 저장한다.
4. getData()를 호출한다.
5. 받아온 데이터가 무엇이든 console을 해야한다.
6. undefined 출력

1.2 undefined가 찍힌 이유

$.get()로 데이터를 요청하고 받아올 때까지 기다려주지 않고 다음 코드인 return tableData;를 실행했기 때문이다. 따라서 getData()의 결과 값은 초기 값을 설정하지 않은 tableData의 값 undefined를 출력한다.

이렇게 특정 로직의 실행이 끝날 때까지 기다려주지 않고 나머지 코드를 먼저 실행하는 것을 비동기 처리라고 한다.

2. setTimeout()

setTimeout()은 코드를 바로 실행하지 않고 지정한 시간만큼 기다렸다가 로직을 실행하는 Web API의 한 종류이다.

// #1
console.log('Hello');
// #2
setTimeout(function () {
	console.log('Bye');
}, 3000);
// #3
console.log('Hello Again');

2.1 비동기 처리에 대한 이해가 없는 상태에서의 예상 결과값

  • 'hello' 출력
  • 3초 있다가 'Bye' 출력
  • 'Hello Again' 출력

2.2 실제 결과값

  • 'Hello' 출력
  • 'Hello Again' 출력
  • 3초 있다가 'Bye' 출력

2.3 실행과정

setTimeout()역시 비동기 방식으로 실행되기 때문에 3초를 기다렸다가 다음 코드를 수행하는 것이 아니라 일단 setTimeout()을 먼저 실행하고 바로 다음 코드인 console.log('Hello Again');으로 넘어간다. 따라서, Hello, Hello Again을 먼저 출력하고 3초가 지난뒤, Bye가 출력되는 것을 볼 수 있다.

Ajax (Asynchronous JavaScript and XML)

Ajax는 자바스크립트를 이용해서 비동기적으로 서버와 브라우저가 데이터를 교환할 수 있는 통신 방식을 의미한다.

Ajax의 특징

서버로부터 웹페이지가 반환되면 화면 전체를 갱신해야 하는데 페이지 일부만을 갱신하고도 동일한 효과를 볼 수 있도록 하는 것이 Ajax이다. 페이지 전체를 로드하여 렌더링할 필요가 없고 갱신이 필요한 일부만 로드하여 갱신하면 되므로 빠른 퍼포먼스와 부드러운 화면 표시 효과를 기대할 수 있다.

브라우저에서 웹페이지를 요청하거나 링크를 클릭하면 화면 갱신이 발생한다. 이것은 브라우저와 서버와의 통신에 의한 것이다.

서버는 요청받은 페이지(HTML)를 반환하는데 이때 HTML에서 로드하는 CSS나 JavaScript 파일들도 같이 반환된다. 클라이언트의 요청에 따라 서버는 정적인 파일을 반환할 수도 있고 서버 사이드 프로그램이 만들어낸 파일이나 데이터를 반환할 수도 있다. 서버로부터 웹페이지가 반환되면 클라이언트(브라우저)는 이를 렌더링하여 화면에 표시한다.

콜백함수 (Callback Function)

어떤 이벤트가 발생한 후, 수행될 함수를 의미한다.

콜백함수의 비유

콜백 함수의 동작 방식은 일종의 식당 자리 예약과 같다. 식당에 자리가 없을 경우, 대기자 명단에 이름을 쓴 다음에 자리가 날 때까지 주변 식당을 돌아다닌다. 만약 식당에서 자리가 생기면 전화로 자리가 났다고 연락이 온다. 그 전화를 받는 시점은 콜백 함수가 호출되는 시점과 같다고 볼 수 있다. 손님 입장에서는 자리가 날 때까지 식당에서 기다리지 않고 근처 가게에서 잠깐 쇼핑을 할 수도 있고 아니면 다른 식당 자리를 알아볼 수도 있다.

자리가 났을 때만 연락이 오기 때문에 미리 가서 기다릴 필요도 없고, 직접 식당 안에 들어가서 자리가 비어 있는지 확인할 필요도 없다. 자리가 준비된 시점, 즉 데이터가 준비된 시점에서만 사용자가 원하는 동작(자리에 앉는다, 특정 값을 출력한다 등)을 수행할 수 있다.

콜백 함수로 비동기 처리 방식의 문제점 해결하기

위에서 자바스크립트 비동기 처리식에 의해 야기될 수 있는 문제들을 살펴보았다. 이러한 문제들은 콜백 함수를 이용하여 해결할 수 있다. 위의 예제인 ajax 통신 코드를 콜백 함수로 개선해보면 이렇게 나온다.

function getData(callbackFunc) {
	$.get('https://jsonplaceholder.typicode.com/posts/1', function (response) {
		callbackFunc(response); // 서버에서 받은 데이터 response를 callbackFunc() 함수에 넘겨줌
	});
}

getData(function (tableData) {
	console.log(tableData); // $.get()의 response 값이 tableData에 전달됨
});

이렇듯 콜백 함수를 사용하게 되면 특정 로직이 끝났을 때의 원하는 동작을 실행시킬 수 있다.

콜백 지옥 (Callback Hell)

콜백 지옥은 비동기 처리 로직을 위해 콜백 함수를 연속해서 사용할 때 발생하는 문제이다.

        // 함수 정의
        function parseValue(response, callback) {
            // 여기에 parseValue 함수의 로직을 구현합니다.
            // 예를 들어, response에서 id 값을 추출한다고 가정합니다.
            var id = response.id;
            callback(id);
        }

        function auth(id, callback) {
            // 여기에 auth 함수의 로직을 구현합니다.
            // 예를 들어, id를 기반으로 인증 결과를 반환한다고 가정합니다.
            var result = (id === 1); // 단순 예시
            callback(result);
        }

        function display(result, callback) {
            // 여기에 display 함수의 로직을 구현합니다.
            // 예를 들어, 결과에 따라 문자열을 반환한다고 가정합니다.
            var text = result ? "Success" : "Failure";
            callback(text);
        }


$.get('https://jsonplaceholder.typicode.com/posts/1', function (response) {
	parseValue(response, function (id) {
		auth(id, function (result) {
			display(result, function (text) {
				console.log(text);
                alert(text);
			});
		});
	});
});

웹 서비스를 개발하다 보면 서버에서 데이터를 받아와 화면에 표시하기까지 인코딩, 사용자 인증 등을 처리해야 하는 경우가 있다. 만약 이 모든 과정을 비동기로 처리해야 한다고 하면 위와 같이 콜백 안에 콜백을 계속 무는 형식으로 코딩을 하게 된다. 이러한 코드 구조는 가독성도 떨어지고 로직을 변경하기도 어렵다. 이와 같은 코드 구조를 콜백 지옥이라고 한다.

콜백 지옥을 해결하는 방법

일반적으로 콜백 지옥을 해결하는 방법에는 Promise나 Async를 사용하는 방법이 있다.
만약 코딩 패턴으로만 콜백 지옥을 해결하려면 아래와 같이 각 콜백 함수를 분리해주면 된다.

        // 함수 정의
        function parseValue(response, callback) {
            // 여기에 parseValue 함수의 로직을 구현합니다.
            // 예를 들어, response에서 id 값을 추출한다고 가정합니다.
            var id = response.id;
            callback(id);
        }

        function auth(id, callback) {
            // 여기에 auth 함수의 로직을 구현합니다.
            // 예를 들어, id를 기반으로 인증 결과를 반환한다고 가정합니다.
            var result = (id === 1); // 단순 예시
            callback(result);
        }

        function display(result, callback) {
            // 여기에 display 함수의 로직을 구현합니다.
            // 예를 들어, 결과에 따라 문자열을 반환한다고 가정합니다.
            var text = result ? "Success" : "Failure";
            callback(text);
        }

function parseValueDone(id) {
	auth(id, authDone);
}
function authDone(result) {
	display(result, displayDone);
}
function displayDone(text) {
	console.log(text);
}
$.get('https://jsonplaceholder.typicode.com/posts/1', function (response) {
	parseValue(response, parseValueDone);
});

위 코드는 앞의 예제를 개선한 코드이다. 중첩해서 선언했던 콜백 익명 함수를 각각의 함수로 구분하였다.

실행과정

1. ajax 통신으로 받은 테이터를 paseValue() 메서드로 파싱한다.
2. paseValueDone()에 파싱한 결과값인 id가 전달되고 auth()메서드가 실행된다.
3. auth()메서드로 인증을 거치고 나면 콜백 함수 authDone()이 실행된다.
4. 인증 결과 값인 result로 display()를 호출한다.
5. 마지막으로 displayDone()메서드가 수행되면서 text가 콘솔에 출력된다.

 

 

Node.js 에서의 실행

Node.js에서 유사한 기능을 수행하려면, axios나 node-fetch와 같은 HTTP 클라이언트 라이브러리를 사용해야 한다. 예를 들어, axios를 사용하는 경우:

 

npm install axios

 

이후에 아래와 같이 코드를 수정한다.

 

const axios = require('axios');

function getData(callbackFunc) {
    axios.get('https://jsonplaceholder.typicode.com/posts/1')
        .then(function (response) {
            callbackFunc(response.data);
        })
        .catch(function (error) {
            console.error(error);
        });
}

getData(function (tableData) {
    console.log(tableData);
});

 

 

주의:

 

제시된 코드는 JavaScript의 Promise 개념을 활용하고 있다. Axios 라이브러리는 HTTP 요청을 비동기적으로 처리하기 위해 내부적으로 Promise를 사용한다. 이 코드에서는 axios.get을 통해 발생하는 HTTP 요청이 Promise를 반환하며, 이 Promise는 요청이 완료될 때까지 기다린 후 결과를 처리한다.

 

다음은 코드에서 사용된 Promise 관련 개념들이다:

  1. Promise 생성: axios.get 함수는 HTTP GET 요청을 보내고, 이 요청의 완료를 나타내는 Promise 객체를 반환한다.
  2. then 메서드: Promise 객체의 then 메서드는 HTTP 요청이 성공적으로 완료되었을 때 실행될 콜백 함수를 등록한다. 이 콜백 함수는 서버로부터의 응답 데이터(response.data)를 받아 처리한다.
  3. catch 메서드: catch 메서드는 요청 중에 오류가 발생했을 때 실행될 콜백 함수를 등록한다. 이는 네트워크 오류나 서버 에러 등 다양한 이유로 요청이 실패했을 때 오류 처리를 위해 사용된다.
  4. 콜백 함수 사용: getData 함수는 외부에서 제공된 콜백 함수(callbackFunc)를 사용하여, HTTP 요청의 결과를 처리한다. 이는 Promise 패턴과 결합하여 비동기 작업이 완료된 후 필요한 추가 작업을 할 수 있게 해준다.

이러한 방식은 JavaScript에서 비동기 작업을 다루는 표준적인 방법 중 하나이며, Promise를 통해 비동기 코드의 가독성과 오류 처리 능력을 향상시킨다.

 

'자바스크립트' 카테고리의 다른 글

JSON  (0) 2023.11.16
this  (0) 2023.11.16
Closure (클로저)  (0) 2023.11.15
원시타입, 참조타입  (0) 2023.11.15
호이스팅 (Hoisting)  (0) 2023.11.15