Node.js에 입문해보자(부록1) : 비동기 처리와 콜백

ButterFlakes·2021년 12월 22일
0

Node.js 입문하기

목록 보기
2/7

시작전에

이 포스팅은 자바스크립트 문법을 어느정도 알고 있다는 가정하에 쓰여진 포스팅입니다
만일 자바스크립트 문법을 모르시면 자바스크립트에 대한 공부를 한 후 오시는 것을 추천드립니다
추천) 생활코딩 - Javascript : https://www.opentutorials.org/course/3085

이 시리즈는 가능하면 ES6의 문법을 사용하려고 노력합니다

본론

import express from 'express'

const app = express()

app.get('/', (request, response) => {
  response.send('Hello, World!')
})

app.listen(3000)

아마 이전에 작성한 코드에서 많은 분들이 이해가 어려운 부분이 app.get 메서드일 것이라고 생각한다
실제로도 블로그 주인장 본인도 저 개념들을 이해하기 위해서 꽤나 고생했었으니 이해가 간다
그렇기 때문에 최대한 덜 해메길 바라면서 이 포스팅을 적었다

도대체 app.get 메서드는 어떻게 돼먹은 녀석인가

아마 app.get의 코드를 분석하는 과정에서 가장 이해하기 힘든 부분은 다름 아닌 두번째 인자로 request, response 객체를 파라미터로 받는 람다식이 오고 그 내부에 동작들이 정의되는 기이한 형태의 코드일 것이다

이러한 형태의 코드를 가리켜 콜백(Callback)이라 부른다
콜백에 대해 알기 위해서는 먼저 몇가지 알아야 할 것이 있다
바로 동기/비동기 처리방식에 대한 것이다

비동기 처리가 뭔데?

비동기 처리 이전에 우리가 흔히 짜던 방식의 코드를 한 번 보도록 하자.

const banana = 'banana'
const apple = 'apple'
const menu = banana + '&' + apple

console.log(menu)

자 이런 코드가 있다. 3번째 줄은 바나나와 사과의 이름을 묶어서 메뉴이름을 만드는 코드이고, 4번째 줄은 메뉴의 이름을 출력하는 코드이다

우리는 이 코드를 읽으면서 당연히 바나나와 사과의 이름을 합쳐서 메뉴의 이름을 만드는 연산을 하는 동안 console.log(menu)를 실행해 뜬금없이 이름을 먼저 출력해버리는 행동이 일어나지 않을 것이라는 것을 알고 있다

이렇게 특정 코드를 모두 처리하기 전까지 다음 코드가 실행되지 않는 형태의 처리방식을 동기 처리라고 부른다

동기 처리는 인간이 본능적으로 행할 수 있는 처리 방식과 다를 게 없기 때문에 매우 쉽고 직관적으로 익힐 수 있다
실제로 다들 프로그램을 배우며 코드를 처음 짰을 때도 3번째 줄이 처리중인데 뜬금없이 4번째 줄의 코드가 실행될 것이라는 생각은 해본 적이 없지 않은가?

아 그래서 비동기 처리가 뭐냐고!

비동기 처리는 이것과 달리 특정 코드의 연산이 모두 처리되길 기다리지 않고 바로 다음 코드로 넘어가서 다음 코드를 실행해버리는 처리 방식을 비동기 처리라고 부른다

엥? 도대체 왜 그런 짓을 하냐고? 우선 이해하기 쉬운 예시를 들어보자

당신이 어떤 유명한 고급 레스토랑에 가려고 한다. 근데 이 레스토랑은 반드시 예약한 손님만 받는데 예약을 못했다고 가정하자. 그래서 레스토랑에 방문해서 혹시 노쇼로 인해 빈 예약이 생기면 연락을 달라 하고 연락처를 남겼다.
그런 다음엔? 레스토랑 앞에 죽치고 앉아서 노쇼가 생기길 기다리는 사람은 아무도 없지 않은가?
대부분은 노쇼 연락이 오기 전까지 다른 일정대로 지내다가 노쇼 연락이 온 시간에 맞춰 레스토랑에 방문할 것이다

비동기 처리 프로세스도 비슷하다
어떤 코드를 실행해 작업을 진행하는데 문제는 이 작업이 언제 끝날 지 예상을 못한다
그럼 마냥 기다리기보단 뒤의 코드를 먼저 실행해버려도 문제가 없다면 그냥 기다리지 말고 뒤의 코드를 실행해버리는게 낭비도 덜하고 좋지 않겠는가?

특히 네트워크 통신을 담당하는 코드들이 그러한 경우가 많다. 아래의 코드를 한 번 보자

const name = 아무튼_프로필_이름_불러오는_함수(URL)
const age = 아무튼_프로필_나이_불러오는_함수(URL)
const profileImage = 아무튼_프로필_이미지_불러오는_함수(URL)

그냥 아무튼 프로필 정보를 불러오는 이 함수들이 네트워크 통신을 하는 코드라고 가정하자

이름을 불러오는 함수를 실행했는데 이 함수는 네트워크 상태에 따라서 금방 끝날 수도, 시간을 낭비하면서 사람이 화딱지가 나게 할 수도 있다
그런데 이름을 불러오기 전에는 절대로 나이와 프로필 이미지를 불러오는 함수를 실행할 수 없다면 얼마나 시간이 낭비 되겠는가?
특히 운이 나쁘게도 나이와 프로필 이미지는 상태가 괜찮은 경로를 따라 데이터가 전송되어서 금방 끝날 수도 있다면? 그럼 이름 정보를 불러왔건 말건 그냥 나이와 프로필 이미지를 불러오는 함수를 실행해 나이와 프로필 이미지를 요청하는게 낫지 않을까?

그래서 비동기 처리는 생각보다 자주 사용된다

이제 콜백(Callback)에 대해 알아보자

비동기 처리는 위에서도 말했듯이 실행이 완료되기 전에 그냥 다음코드로 홱 하고 넘어가버린다
그럼 문제가 하나 생기는데 반드시 비동기로 처리되는 코드의 실행이 완료되고 나서 실행되어야 할 코드가 있다면 어떻게 될까?

우선 아래의 코드를 한 번 보자

let name
setTimeout(() => {
  name = 'ButteFlake'
}, 3000)
console.log(name)

setTimeout은 잠깐 정해진 시간만큼 멈췄다가 다시 코드를 실행시키는 함수이다

그렇다면 대부분은 아마 3초간 멈췄다가 콘솔창에 ButteFlake 라는 글자가 출력될 것이라고 생각할 것이다

하지만 실제 출력 결과는 undefined이다
왜 이런 결과가 나오는 걸까?

위의 setTimeout 역시 비동기로 처리되는 함수이다. 그렇기 때문에 3초간 기다리는 명령만 실행시켜놓고 바로 console.log(name) 으로 넘어가버리는 것이다, 3초간 기다려 name에 ButterFlake를 대입하는 명령이 실행되기 전에

하지만 우리는 3초간 기다려 name에 ButterFlake 가 대입된 이후 ButterFlake 라는 글자가 출력되길 기대하고 있다
그렇다면 어떻게 해야 할까?

우선 setTimeout 함수를 다시 한 번 보자

setTimeout(() => {
  name = 'ButterFlake
}, 3000)

보면 setTimeout 함수의 첫번째 인자로 람다식이 주어지고 그 내부에 대입 연산자가 있다.

실제로 3초뒤에 실행되는 것은 이 부분이다. 정말인지 궁금하면 코드를 아래와 같이 작성하고 한 번 실행시켜보자

console.log(1)
setTimeout(() => {
  console.log(2)
}, 3000)
console.log(3)

과연 결과가 어떻게 나올까?

이미 위에서 경험에서 다들 예상했겠지만, 1 - 3 - (3초대기) - 2 의 순서대로 출력되었다
위에서 말한대로 3초가 지나서 람다식 내부에 있는 console.log(2) 가 실행되었다
람다식 내부의 코드들이 실제로 3초 이후에 실행되는 부분이라는 소리이고, 이러한 코드를 콜백이라 부른다

이것이 바로 위에서 말한 "그럼 반드시 비동기 처리 코드의 실행이 완료되고 나서 실행되어야 할 코드가 있다면 어떻게 해야되나" 에 대한 대답이다.
노드의 비동기처리 코드 상당수는 이러한 콜백을 정의할 수 있도록 되어있다

그럼 이 코드를 다시 한 번 보자

app.get('/', (request, response) => {
  response.send('Hello, World!')
})

이제 비동기 처리와 콜백의 작동방식을 알아보았으니 대충 어떻게 동작할 것인지 알 것이다
첫번째 인자가 의미하는게 뭔지는 잘 모르겠고, 아무튼 일단 이 메서드가 어떻게 돌아가는지는 자세히 몰라도 내부에서 처리를 하고 request, response 객체를 인자로 받는 콜백 함수를 호출해서 비동기 처리 후의 작업들을 진행한다는 사실을 유추할 수 있다

이제 서버로 연결했을 때 실행할 동작들을 정의하고 싶다면 콜백 내부에다 코드를 정의해 주면 된다. 그럼 대부분은 의도한 대로 작동할 것이다

앞으로도 이렇게 콜백을 넘겨주는 메서드를 쓸 때는 콜백 내부에다가 비동기 처리 이후에 실행될 코드들을 정의해주면 된다

번외편: 콜백 지옥(Callback Hell)

비동기 처리 메서드나 함수에서 콜백은 비동기 처리 이후에 실행될 동작들을 정의한 함수이다

만약 비동기 처리 이후에 또 비동기 처리를 해야 한다면? 그럼 콜백 내부에 비동기 처리 메서드를 작성하고 또 콜백을 정의해 해당 비동기 처리 메서드 이후에 실행될 코드들을 정의해주어야 한다

그렇게 계속 콜백 내부에 콜백을 물고 들어가면 아래와 같은 끔찍한 코드가 탄생한다

보기만해도 어지럽다!

이런식으로 콜백 내부에 콜백을 물어서 코드가 오른쪽 아래방향 대각선으로 자라나는걸 콜백 지옥이라 부른다

이런 콜백 지옥이된 코드는 보기에도 어지러울 뿐더러 수정도 쉽지 않고, 다른 사람이 이 코드를 보고 해석하거나 유지보수를 할 때 매우 Jot같은 경험을 선사해준다

때문에 이런 콜백 지옥을 예방하기 위한 여러가지 방법들이 있는데 함수단위로 분리해주는 방법도 있고, Promise를 사용하는 방법도 있고, async/await를 이용하는 방법도 있다

하지만 Promise, async/await를 여기서 다루었다간 튜토리얼이란 범위를 벗어나게 되니 나중에 다루도록 하고 오늘 포스팅은 여기서 마치겠다

0개의 댓글