본문 바로가기

Programming/TIL

Async & Callback & Promise

Achievement Goals

  • 어떤 경우에 중첩된 callback이 발생하는지 이해할 수 있다.
  • 중첩된 callback의 단점, Promise 의 장점을 이해할 수 있다.
  • Promise 사용 패턴과 언어적인 특징들을 이해할 수 있다.
    • resolve, reject의 의미와, then, catch와의 관계를 이해할 수 있다.
    • Promise 에서 인자를 넘기는 방법에 대해 이해할 수 있다.
    • Promise의 세가지 상태를 이해할 수 있다.
    • Promise.all 의 사용법을 이해할 수 있다.
  • async/await keyword에 대해 이해하고, 작동 원리를 이해할 수 있다.
  • node.js의 fs 모듈의 사용법을 이해한다.

Asynchronous

 

비동기가 무엇인지에 대해 먼저 알아보자.

비동기는 우리 생활속에서 이미 많이 사용이 되고 있다.

예를 들어서, 동영상이 로딩 되는 동안 우리는 사이트의 다른 메뉴들이나 기타 영상들, 댓글 등을 볼 수 있다.

그렇듯, 백그라운드 실행, 로딩 창 등과 같이 이미 실생활에서 느끼고 있는 것들이 모두 비동기라 할 수 있다.

비동기란, 즉, 무작정 그 작업이 끝날때까지 기다려야만 하는 것이 아니다.

 

또 한가지 예시로, 카페에서 주문하는 것 또한 비동기적이다.

손님1이 와서 아메리카노를 주문하고, 그 뒤의 손님2가 주문하고, 이어서 다음 손님들도 주문을 먼저 받는 형태가 정상적이다.

이런 주문 처리 방법이 비동기적이다.

 

반면에, 손님1이 주문한 아메리카노를 받을 때까지 뒤에 대기하는 손님들은 주문도 못하고 기다리게 되는 경우는 일어나면 안되겠지만, 이 경우가 바로 동기적으로 실행되는 것이다.

 

동기적으로 실행이 된다는 것은 Blocking이 되며, Server에 보낸 요청이 끝나기 전까지 Client는 아무것도 못하고 기다려야 한다.

 

자, 비동기에 반대되는 개념까지 간단하게 알아보았다. 

위의 예시 이외에도 인터넷에서의 요청, 큰 용량의 파일을 로딩하는 등의 작업을 하기 위해서는 비동기 처리가 반드시 필요하다.

그리고, 비동기는 node.js의 핵심 부분이므로, node.js의 모듈 사용법 또한 천천히 익히는 것이 좋다.

 

다음은, 비동기의 중요한 개념들 몇 가지에 대해서 알아보자.

1) Callback

2) Promise

3) Async & Await


Callback

 

아래 예시 코드를 보면서 비동기에서의 Callback을 설명하겠다.

위에 언급했던 비동기의 효율은 좋으나, 만약 순서를 가지거나 제어하고 싶다면 어떻게 할 수 있을까?

 

내가 원하는 순서대로 각각 콜백함수를 적어도 Math.random()이라는 놈 때문에, 실행 시간에 따라 출력되는 결과가 매번 다를 것이다.

하지만,  callback의 callback의 callback으로 적는다면 실행 소요 시간과 관계 없이 아래처럼 나열한 콜백의 순서대로 출력이 되게 된다.

const printString = (string, callback) => {
    setTimeout(
      () => {
        console.log(string)
        callback()
      }, 
      Math.floor(Math.random() * 100) + 1 
      // Makes the string have random processing time
    )
  }
 
 const printAll = () => {
    printString("A", () => {
      printString("B", () => {
        printString("C", () => {})
      })
    })
  }
  printAll() // Same ordered result
  // A, B, C
  // A, B, C
  // A, B, C 
  

Callback으로 순차적으로 불러올 수 있는 사용은 매우 좋으나, 콜백 함수 사용이 많아지는 경우, 계속 Indentation 효과로 밀려나게 된다.

그 결과, 아래와 같은 불-편한 코드가 나오게 되고, 이를 흔히 Callback Hell이라고 부른다.


Promise

 

Callback에서 언급했던 Callback Hell을 다시 생각해보자.

매우 길게 늘어져있고, 한 콜백의 중괄호의 짝이 어디있는지 찾기도 왠만해서 쉽지 않다.

이렇게 callback끼리 연결되있는 것을 우리는 Callback Chain이라고 칭한다.

 

Promise의 사용으로 길게 늘어지는 Callback Chain을 제어할 수가 있게 된다.

Promise는 '약속'이란 뜻을 가지고 있는 Class로서, new Promise()를 통해 인스턴스를 만들어 준다.

 

Promise는 아래 그림과 같이 두개의 커맨드가 있다.

- resolve()

- reject()

 

두 명령어를 통해서 다음 동작으로 넘어가거나, 에러를 핸들링을 할 수 있다.

그림을 통해서 다시 이해해보자.

new Promise()
.then 사용법
Promise의 세 가지 상태와 흐름

밑에 코드는 Callback Chain으로 만든 것을 Promise를 통해 재구현을 한 결과다.

.then()을 통해서 각각의 콜백을 호출해준 것이다.

또한, .then을 사용함으로 줄 간격도 일정하게 맞출 수가 있는 장점도 있다.

확인해 본 것처럼, .then()은 성공적으로 작업이 실행될 때 사용이 된다.

반면, 에러 발생 시, .catch()를 통해 에러를 잡아줄 수 있다.

const printString = (string) => {
    return new Promise((resolve, reject) => {
      setTimeout(
        () => {
         console.log(string)
         resolve()
        }, 
       Math.floor(Math.random() * 100) + 1
      )
    })
  }

 const printAll = () => {
    printString("A")
    .then(() => {
      return printString("B")
    })
    .then(() => {
      return printString("C")
    })
  }
  printAll()
function gotoCodestates() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('1. go to codestates') }, Math.floor(Math.random() * 100) + 1)
    })
}

function sitAndCode() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('2. sit and code') }, Math.floor(Math.random() * 100) + 1)
    })
}

function eatLunch() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('3. eat lunch') }, Math.floor(Math.random() * 100) + 1)
    })
}

function goToBed() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('4. goToBed') }, Math.floor(Math.random() * 100) + 1)
    })
}

gotoCodestates()
.then(data => {
    console.log(data)
    return sitAndCode()
})
.then(data => {
    console.log(data)
    return eatLunch()
})
.then(data => {
    console.log(data)
    return goToBed()
})
.then(data => {
    console.log(data)
})

위 코드는 깔끔한 Promise Chaining의 예시이다.


Async & Await

 

여기서 등장하는 Async는 '비동기'이고, Await은 '기다리다'라는 의미를 가지고 있다.

방금 다뤘던 동일한 코드를 Async와 Await을 사용해 만들은 코드를 가져왔다.

function gotoCodestates() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('1. go to codestates') }, Math.floor(Math.random() * 100) + 1)
    })
}

function sitAndCode() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('2. sit and code') }, Math.floor(Math.random() * 100) + 1)
    })
}

function eatLunch() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('3. eat lunch') }, Math.floor(Math.random() * 100) + 1)
    })
}

function goToBed() {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve('4. goToBed') }, Math.floor(Math.random() * 100) + 1)
    })
}

const result = async () => {
    const one = await gotoCodestates();
    console.log(one)

    const two = await sitAndCode();
    console.log(two)

    const three = await eatLunch();
    console.log(three)

    const four = await goToBed();
    console.log(four)
}

result();

 

'Programming > TIL' 카테고리의 다른 글

Data Structure(5) Graph  (0) 2020.12.07
Data Structure(4) Tree  (0) 2020.12.07
Data Structure(3) Hash Table  (0) 2020.12.04
Data Structure(2) Linked List  (0) 2020.12.04
Big O  (0) 2020.12.03