JavaScript 2. 동기/비동기 1

jiffydev·2020년 11월 30일
1

들어가기 전에

여태껏(이래봐야 2개월이지만) 파이썬-장고 조합으로 백엔드 프로젝트를 진행해 왔다. 그런데 한국에서 장고는 마이너 of 마이너라 쓰는 회사도 거의 없는 듯하여 스타트업에서 많이 쓴다는 Node.js를 공부해 보려고 한다. 그런데 이 nodejs가 자바스크립트 언어를 기반으로 하는 녀석인데, 자바스크립트는 공부한지가 너무 오래되어 다 까먹은 상태라 자바스크립트부터 다시 시작하려고 한다.

nodejs를 시작함에 있어 필수적으로 알아야 될 개념 & 함수가 있다고 하는데, 그 중 하나가 동기/비동기 개념이다. 장고로 개발을 할 때는 아직 초보단계라 그런지 비동기 처리를 쓸 일이 거의 없었고, 장고 자체에서 비동기를 지원한 것이 극히 최근(3.1버전)이라 외부 라이브러리(celery 등)를 사용하는 터라 개념이 익숙하지 않았기에 정리해 본다.

동기? 비동기?

자바스크립트가 어쩌고 하기 전에 동기/비동기 개념을 먼저 알아보는 것이 순서일 듯하다. 제일 먼저 머리속에서 지워야할 생각은 '동기=동시에 실행' 이라는 생각이다. 함수들이 동기적으로 실행된다고 하면 가장 먼저 떠오르는게 '동시에 실행된다'는 생각인데(나만 그런가?) 오히려 그 반대이다.

함수 A, B, C, D를 위에서 아래로 작성했을 때, 동기적으로 실행된다면 A->B->C->D의 순서대로 실행된다. 따라서 Synchronous라 함은 순차적으로 실행된다고 인식하면 될 것이다.
반대로 비동기적으로 실행된다면 B->A->D->C, A->C->B->D 등등 작업량 등에 따라 실행 순서가 바뀌게 된다.

당연히 동기적으로 실행되는 것이 이해하기도 쉽고 코드를 작성하기도 쉽다. 하지만 인생은 우리를 그렇게 쉽게 살도록 놔두지 않는다. 일상 생활에서의 예를 들어보면, 우리가 해야 할 집안일에 빨래, 청소, 화분에 물주기가 있다고 할 때, 빨래를 돌리기 시작해서 빨래가 다 끝나야 청소를 하고, 청소가 다 끝나야 물주기를 하는 경우는 많지 않을 것이다. 일반적으로는 빨래를 돌려 놓고 그 동안 다른 작업들을 처리할 것이다.

코드도 이와 마찬가지이다. 동기적으로만 실행하면, 오래 걸리는 작업이 있을 경우 그 작업 때문에 빨리 끝날 수 있는 다른 작업들을 끝내지 못하므로 처리 속도가 느려지게 되고, 웹페이지에서 기나긴 로딩페이지를 보고 있어야 할 것이다.

In JavaScript?

자바스크립트에서는 자체적으로 비동기적으로 처리할 수 있는 방식을 준비해 두고 있다.

1. Callback


콜백 함수는 다른 함수의 인자로 들어가 나중에 실행되는 함수를 뜻하는데, 결국 이것이 비동기적으로 실행됨을 뜻한다.(사실 콜백함수는 동기적일 수도 비동기적일 수도 있다)
비동기적으로 실행되는 콜백함수는 setTimeOut이나 setInterval을 사용해서 다음과 같이 만들 수 있다.

function first() {
  console.log(1)
}

function second(callback) {
  setTimeout(() => {
    console.log(2)

    // Execute the callback function
    callback()
  }, 0)
}

function third() {
  console.log(3)
}

first()
second(third) // third()가 콜백함수로 실행됨(인자로 들어가는 콜백함수는 괄호 없이)

// result
1
2
3

순서대로 실행됐는데 뭐가 비동기적이냐고 할 수 있겠지만, 사실 콜백함수로 넣지 않고 그냥

first()
second()
third()

와 같이 실행했다면 second함수의 setTimeout으로 인해 결과는 1 3 2순서로 나올 것이다.(물론 second의 콜백함수 내용은 삭제)
이처럼 콜백함수로 비동기적인 처리를 할 수 있고, 현재도 많이 쓰이는 방법이지만 콜백함수 안에 콜백함수가 있고 또 그 안에 콜백함수.. 이런식으로 복잡해지면 그 유명한 콜백지옥이 다음과 같이 탄생한다.

function asynchronousRequest(args, callback) {
  // Throw an error if no arguments are passed
  if (!args) {
    return callback(new Error('Whoa! Something went wrong.'))
  } else {
    return setTimeout(
      // Just adding in a random number so it seems like the contrived asynchronous function
      // returned different data
      () => callback(null, {body: args + ' ' + Math.floor(Math.random() * 10)}),
      500,
    )
  }
}

// Nested asynchronous requests
function callbackHell() {
  asynchronousRequest('First', function first(error, response) {
    if (error) {
      console.log(error)
      return
    }
    console.log(response.body)
    asynchronousRequest('Second', function second(error, response) {
      if (error) {
        console.log(error)
        return
      }
      console.log(response.body)
      asynchronousRequest(null, function third(error, response) {
        if (error) {
          console.log(error)
          return
        }
        console.log(response.body)
      })
    })
  })
}

// Execute
callbackHell()

이것도 간단한 예제이지만 벌써 보기가 싫어진다. 이처럼 콜백함수를 몇 번 겹치다 보면 디버깅도 힘들어지고 무엇보다 가독성이 떨어지기 때문에 이런식의 사용은 지양하는 것이 바람직하다.

다음 글에서는 더 편하게 비동기 처리를 할 수 있는 함수를 소개하고자 한다.

profile
잘 & 열심히 살고싶은 개발자

0개의 댓글