함께 성장하는 프로독학러

1. 동기와 비동기, 콜백함수 본문

Programming/Node.js

1. 동기와 비동기, 콜백함수

프로독학러 2018. 5. 23. 17:11

안녕하세요, 프로독학러 입니다.


이번 포스팅에서는 Node.js 를 이해하는데 필수적인 개념인 콜백함수와 동기, 비동기적 처리에 대해서 알아보도록 하겠습니다.


저번 포스팅에서 Node.js 의 특징 중 첫 번째가 무엇이었는지 기억하시나요?


그것은 바로 '비동기적 I/O 처리, 이벤트 위주' 였습니다.


* I/O 는 input, output 을 의미합니다.


동기 / 비동기


비동기적 I/O 처리. 저번 포스팅에서 Node.js 의 특징에 대해서 설명하면서 간단히 설명했지만 아직 감이 안 잡히시는 분들이 많을 것입니다.


비동기의 반댓말은 동기 입니다. 이를 영어로 표현하면 다음과 같습니다.


  • Synchronous (동기)
  • ASynchronous (비동기)


보통 프로그래밍에서 코드의 실행은 작성된 코드의 위에서 부터 아래로 진행됩니다.


동기적 처리(Synchronous)


다음과 같은 코드를 작성하고 실행해 봅시다.


(sync_test.js)

1
2
3
4
5
console.log("1st");
 
console.log("2nd");
 
console.log("3rd");
cs


코드가 위에서 부터 아래로 순차적으로 실행되므로 콘솔에 찍힐 것으로 예상되는 결과는,

1st, 2nd, 3rd 순서일 것입니다.


그럼 실제로 실행시켜 위의 코드가 우리가 기대했던대로 작동하는지 알아봅시다.



우리가 위에서 예상했던 것 처럼 1st, 2nd, 3rd 가 차례로 찍히는 것을 알 수 있습니다.


이처럼 코드가 위에서부터 아래로 내려오며 하나가 끝나면 다음 코드가 실행되는 방식을 동기적 처리(Synchronous) 라고 합니다.


비동기적 처리(Asynchronous)


그렇다면 비동기적 처리는 어떤 것을 의미할까요? 

코드를 통해 살펴봅시다.


(async_test.js)

1
2
3
4
5
6
7
console.log("1st");
 
setTimeout(()=>{
  console.log("2nd");
}, 0)
 
console.log("3rd");
cs


코드의 세 번째 줄에서 setTimeout() 메소드를 사용했습니다. 

setTimeout() 메소드의 첫 번째 인자는 콜백함수, 두 번째 인자는 지연시간입니다.

(콜백함수에 대해서는 아래에서 자세히 알아보도록 하겠습니다)

두 번째 인자가 0으로 지정되었으므로 곧바로 실행될거라고 예측할 수 있습니다.


따라서 위의 코드의 실행결과는 1st, 2nd, 3rd 가 순서대로 콘솔에 찍힐 것이라고 예상할 수 있습니다.


그럼 실제로 그렇게 콘솔에 찍히는지 확인해 봅시다.



콘솔에는 1st, 2nd, 3rd 가 아닌 1st, 3rd, 2nd 가 찍혔습니다.


이는 setTimeout() 메소드가 비동기적 API 이기 때문입니다.

위의 async_test.js 코드를 실행하는 과정을 컴퓨터 입장에서 설명하면 다음과 같습니다.


  1. 첫 번째 줄에서 console.log("1st"); 만나고, 콘솔에 1st 를 찍는다.
  2. 세 번째 줄에서 setTimeout() 메소드를 만난다. 해당 메소드가 비동기적 메소드임을 파악하고, 이를 비동기를 처리하는 다른 프로그램에게 위임한다.
  3. 위임하고 난 뒤에,  곧바로 다음코드(7번째 줄)을 실행하여 3rd 를 콘솔에 찍는다.
  4. setTimeout() 메소드를 위임받아 처리한 프로그램은 "비동기적 API를 제외한 모든 코드가 실행된 이후"에, 결과를 콘솔에 2nd 를 찍는다.


위와 같은 과정을 통해 코드가 실행되기 때문에 setTimeout() 메소드의 두 번째 인자가 0 이어도(시간 지연 없이 바로 실행된다 하더라도) 콘솔에는 1st, 3rd, 2nd 가 찍히는 것입니다.


(async_test.js)

1
2
3
4
5
6
7
8
9
10
11
12
console.log("1st");
 
setTimeout(()=>{
  console.log("2nd");
}, 0)
 
console.log("3rd");
console.log("4th");
console.log("5th");
console.log("6th");
console.log("7th");
console.log("8th");
cs


만약 코드가 위와 같더라도 2nd 는 맨 나중에 찍히는 것을 알 수 있습니다.



비동기적 코드의 실행 결과는 동기적 코드의 실행이 완료되면 값을 반환한다.


* 자바스크립트의 비동기 처리방법에 대해 읽어보신다면 이해가 더 쉬울 수 있습니다.

<자바스크립트의 비동기 처리방식>


비동기적 처리의 효용


그렇다면 이러한 비동기적 처리가 필요한 이유는 무엇일까요?


제이쿼리의 ajax 통신과 같이 원하는 데이터를 서버로 부터 받아오는 방식을 취하는 어플리케이션을 만들었다고 가정해 봅시다.


서버로부터 데이터를 받아와서 해당 데이터를 처리해야 하므로, 데이터를 받아오는 코드는 전체 코드중의 최상단에 위치해야 할 것입니다. 그런데 만약 비동기적 처리를 하지 않고 동기적 처리를 하는 어플리케이션이라면 어떨까요? 서버로부터 데이터를 받아오는 코드의 실행이 완전히 끝난 뒤에 이후의 코드를 처리할 것입니다.


받오는 데이터의 크기가 작고 갯수가 적다면 크게 상관없을 수 있지만, 만약 받아오는 데이터의 크기가 크고 많다면 어떨까요?


모든 데이터의 수신을 완료한 다음에야 나머지 코드를 실행할 수 있으므로 데이터를 받아오는 동안에 프로그램은 사실상 멈춰있는것과 다름없습니다. 언제 끝날지도 모르는 요청때문에 나머지 코드를 실행시키지 못하고 사용자를 기다리게 하는 아주 불친절한 프로그램이 되어 버리겠죠. 데이터를 수신해야할 때마다 기다려야 한다면 누구도 그 어플리케이션을 사용하고 싶지 않을것입니다.


이러한 사용자의 불편을 없애기 위해 데이터의 수신과 기다려야하는 코드들을 비동기적으로 처리하는 것입니다.


콜백함수


그럼 이제 콜백함수에 대해서 알아보도록 하겠습니다.


콜백함수는 특정 함수에 매개변수로 전달된 함수를 의미합니다. 그리고 그 콜백함수는 함수를 전달받은 함수안에서 호출됩니다.


간단히 표현하면 다음과 같습니다.


(callback_test.js)

1
2
3
4
5
6
const testCallback = function (callback) {
  console.log("inside of testCallback func");
  callback()
};
 
testCallback(function(){console.log("this is callback")});
cs


testCallback 함수를 정의 할 때 함수의 인자로 callback 이라는 매개변수를 정의 했습니다.

이 callback 은 함수로, testCallback 이 실행되면 함수안에서 다시 실행됩니다. (3번째 줄에서 실행)

그리고 6번째 줄에서 testCallback 함수를 실행할 때 그 인자로 익명함수를 주었습니다.

해당 함수는 testCallback 안에서 실행되어 "this is callback" 이라는 문자를 콘솔에 찍습니다.



코드를 실행하면 위와 같이 찍힙니다.


콜백함수의 예를 한 가지 더 살펴봅시다. 


(callback_test.js)

1
2
3
4
const a = [312];
 
a.sort();
console.log(a);
cs


코드의 첫 번째 줄에서 a 라는 상수에 배열 [3, 1, 2] 를 할당했습니다.

세 번째 줄에서 배열의 sort 메소드를 이용하여 a 배열을 정렬했습니다.

네 번째 줄에서 콘솔에 a 를 찍었습니다.


이를 실행시켜 보면 다음과 같습니다.



배열 a 가 오름차순으로 정렬되어 [1, 2, 3] 이 되었습니다.


이 코드를 조금 수정해 보도록 하겠습니다.


(callback_test.js)

1
2
3
4
const a = [312];
 
a.sort(function(a, b){return b-a});
console.log(a);
cs


코드를 실행시켜보면, 결과는 다음과 같습니다.



sort 메소드에 인자로 비교함수를 주자 a 의 정렬방식이 내림차순으로 변경되었습니다.


배열에 sort 메소드는 인자로 비교함수를 받는데, 이는 선택사항으로 주지 않아도 되는 값입니다.

하지만 인자로 함수를 준다면, 그 함수의 리턴값이 음수인지 양수인지 0인지에 따라 정렬의 방식을 다르게 합니다.

* 배열의 sort 메소드에 대해서 자세히 알고 싶으신 분은 아래의 링크를 참조해 주세요

<배열의 sort 메소드>


sort 메소드는 내부적으로 아래와 같은 모습을 하고 있을 것입니다.


1
2
3
4
function sort(callback) {
    ...
    callback();
}
cs


콜백함수를 사용하는 이유?


콜백함수가 함수의 인자로 전달되는 함수이며, 전달받은 함수내에서 실행된다는 것은 알겠습니다.

그렇다면 왜 콜백함수를 사용하는 것일까요?


예제를 통해 살펴보겠습니다.


1
2
3
4
5
6
7
8
9
function getData() {
    var tableData;
    $.get('https://domain.com/products/1'function (response) {
        tableData = response;
    });
    return tableData;
}
 
console.log(getData()); // undefined
cs


위와 같은 ajax 코드가 있다고 생각해 봅시다. 

getData 함수는 $.get 메소들를 통하여 'https://domain.com/products/1' 에서 데이터를 수신해 옵니다.

수신한 데이터를 2번째 줄에서 정의한 tableData 변수에 할당하고, tableData 를 리턴합니다.


코드의 9번째 줄에서 getData 함수를 실행하면 'https://domain.com/products/1' 를 통해 받아온 데이터가 콘솔에 찍힐것이라 예상되지만, 콘솔에는 undefined 가 찍힙니다.


이는 $.get 메소드는 비동기적 메소드이기 때문에 발생하는 문제입니다.

코드가 실행되다 $.get 메소드를 만나면 $.get 메소드를 다른 프로그램에 위임하고 다음코드인 tableData 를 리턴해 버립니다. 즉, tableData 에는 아무런 값도 할당되지 않은 채로 함수가 끝나버리므로 콘솔창에는 undefined 가 찍히는 것입니다.


이러한 문제를 콜백함수를 통해 해결할 수 있습니다.


1
2
3
4
5
6
7
8
9
function getData(callbackFunc) {
    $.get('https://domain.com/products/1'function (response) {
        callbackFunc(response); // 서버에서 받은 데이터 response를 callbackFunc() 함수에 넘겨줌
    });
}
 
getData(function (tableData) {
    console.log(tableData); // $.get()의 response 값이 tableData에 전달됨
});
cs


위의 코드에서는 getDate 함수를 정의 할 때 파라메터로 callback 함수를 주었습니다.

그리고 파라메터로 지정된 콜백함수는 $.get 메소드 안에서 실행 되었습니다.

$.get 메소드는 서버에서 받은 데이터를 콜백함수의 인자로 넘겨줍니다.


7번째 줄에서 getData 를 실행할 때 인자로 익명함수를 주었는데, 그 익명함수는 전달받은 인자를 콘솔에 표시하는 함수입니다.

(콜백함수를 실행단계에서 정의)


콜백함수를 통해 $.get 메소드의 비동기적 처리로 인한 문제를 해결할 수 있게 되었습니다.


즉, 콜백함수를 사용하기 이전의 코드는 데이터가 준비되지 않은 상태에서 함수가 끝나기 때문에 원하는 대로 동작하지 못하지만, 콜백함수를 사용하면 필요한 데이터가 다 준비된 시점에서만 원하는 동작을 수행하도록 할 수 있습니다. 


여기까지...


동기, 비동기적 처리와 콜백함수에 대해서 알아보았습니다.

Node.js 에서는 비동기적 처리와 콜백함수를 많이 사용하기 때문에 꼭 짚고 넘어가야할 개념중에 하나였습니다.


간단히 정리하자면, 비동기적 처리를 통해 코드의 실행을 멈추지 않고 계속 실행하게 하며, 비동기적 처리를 통해 어떤 동작을 수행할 때, 콜백함수는 동작에 필요한 준비물이 준비된 상태에서만 동작을 수행하도록 할 수 있는 함수이며, 이를 통해 비동기 함수의 결과를 반환합니다.


다음 포스팅에서는 Node.js 를 통해 웹 어플리케이션을 만드는데 많이 사용되는 프레임 워크인 Express 에 대해서 알아보도록 하겠습니다.


감사합니다.


**참고 자료 (항상 감사드립니다)

https://opentutorials.org/course/2136

https://velopert.com/255

https://joshua1988.github.io/web-development/javascript/javascript-asynchronous-operation/



*이 포스팅이 도움이 되셨다면 다녀가셨다는 표시로 공감 부탁드릴게요! (로그인 하지 않으셔도 공감은 가능합니다 ㅎㅎ)


Comments