[Javascript] 비동기 개념 학습

Yalstrax·2021년 6월 24일
40

Front End

목록 보기
8/8
post-thumbnail

1. 비동기 🎏

동기적(Synchronous)이란 것은 무엇일까요?
어떤 예시를 들어야 할지 고민하다가, 리그오브레전드 게임을 예시로 들어보겠습니다. (제가 이해한 바로 적는 내용이니 틀렸다면 지적해주시면 감사하겠습니다!)

리그오브레전드(이하 LOL)에는 리 신 이라는 챔피언이 있습니다.
스타일리쉬한 스킬 구성으로 오랫동안 사랑받고있는 챔피언인데요, 이 리 신의 여러 콤보 중 하나 다음과 같습니다.

1 Q(음파/공명의 일격)를 적 챔피언에게 사용한다.
2 적중한 경우, R(용의 분노)을 사용한다.
3 Q(음파/공명의 일격)를 사용한다.

위 세 단계의 과정을 거치는 콤보가 있습니다.
이 콤보는 먼저 Q를 사용해(시작) 적중시키고(완료) R을 사용합니다.(시작, 완료)
그리고 Q를 사용해(시작) 적에게 피해를 입힙니다.(완료)

위 콤보는 일련의 순서가 있고, 그 과정이 완료된 시점에 다음 과정이 시작됩니다. 이처럼 시작 시점과 완료 시점이 같은 상황을
동기적(Synchronous)이라 합니다.

비동기적(Asynchronous)이란 것은 무엇일까요?
리 신을 조금 친다는 분들, 고수분들은 아래와 같은 콤보를 사용해 전투를 유리한 흐름으로 가져올 수 있습니다.


1 Q(음파/공명의 일격)를 적 챔피언에게 사용한다.
2 적중한 경우, Q(음파/공명의 일격)를 사용한다.
3 R(용의 분노)을 사용한다.
3 점멸을 사용한다.

위 콤보는 일련의 순서가 있지만, 3단계가 두번 수행됩니다.
위와 같이 콤보를 사용한다면, 리 신이 R(용의 분노)를 사용하기 위해 스킬을 준비하는 도중, 점멸을 사용해 적 챔피언의 뒤로 이동해
원하는 위치로 적 챔피언을 걷어찰 수 있습니다.

위 콤보와 다른 점은, R(용의 분노)가 끝나고 점멸을 사용한게 아닌, R이 실행되는 도중 점멸을 사용했고, 먼저 실행한 R보다 점멸이
완료되고, R이 완료되었습니다. 이 콤보는 비동기적(Asynchronous) 리 신 콤보 라고 할 수 있겠습니다.

이를 비동기적(Asynchronous)라고 합니다. 일련의 순서대로 진행이 아닌, 효율적인 작업을 위해 어떤 작업을 실행시키고 그 작업이 실행되는 동안 다른 작업을 수행 할 수 있습니다.

비동기적 프로그래밍의 주요 사례는 아래와 같습니다.

  • DOM Element의 이벤트 핸들러
    마우스, 키보드 입력 (onclick, onkeyup 등)
    페이지 로딩 (DOMContentLoaded 등)
  • 타이머
    타이머 API (setTimeout 등)
    애니메이션 API (requestAnimationFrame)
  • 서버에 자원 요청 및 응답
    fetch API
    AJAX (XHR)

1.1 Callback 📠

2를 요소로 가진 길이가 4인 배열이 있습니다.

let arr = [2,2,2,2];

이 배열에 2를 곱한 값, 즉 각 요소에 2를 곱한 값을 반환할 땐 우리는 배열의 메소드 중 map또는 forEach를 사용할 수 있습니다.

let squareArr = arr.map((el) => el * 2);
console.log(squareArr) // [4, 4, 4, 4]

map 메소드는 보시는 것 처럼 인자로 함수 (el) => el * 2 를 받습니다.
이 함수는 el이라는 입력인자를 받아, 배열의 각 요소 el에 2를 곱한 값을 반환합니다.

이처럼 고차함수의 인자로 전달되는 함수를 콜백(Callback) 함수라고 합니다.
콜백 함수를 인자로 받은 함수를 Caller 함수라고 하며, map은 각 요소에 2를 곱하는 콜백 함수의 Caller가 됩니다.

const printString = (string, callback) => {
    setTimeout(
        () => {
            console.log(string)
            callback()
        },1000)
}

printString이라는 함수를 만들고, 인자로 stringcallback함수를 받습니다.
이 함수는 setTimeout이라는 Web API를 사용해 1000ms (1sec) 후 입력인자로 받아온 string을 반환하고,
callback함수를 실행합니다.

const printAll = () => {
    printString('a',() => {
        printString('b', () => {
            printString('c', () => {})
        })
    })
}
printAll();

printAll이라는 함수는 실행하게 되면, printString 함수를 실행하고, callback 함수로 들어온 printString 함수를 실행하고, 그 다음 callback으로 들어온 printString함수를 실행합니다.

결과는 string 으로 들어온
a,b,c 가 1초 간격으로 console창에 표기됩니다.

위 아주 간단한 예시인 callback함수도 마지막 부분에 })가 다수 반복되는데, 실제 현업에서 callback을 사용한다면 어떨까요?

아마 다음과 같이 가독성이 매우 떨어지는 이른바 callback hell을 마주할 것입니다.

이러한 불편함을 개선하기 위해 Promise class를 사용할 수 있습니다.

1.2 Promise 🤝

promisecallback chain을 핸들링할 수 있는 하나의 Class입니다.
promiseresolvereject로 함수를 실행하거나, 에러를 핸들링할 수 있습니다.

const printString = (string) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(string)
            resolve()
        },
                   1000)
    })
}

위 함수는 string을 입력인자로 받고, Promise class를 반환합니다.
setTimeout은 위와 동일하며, callback을 실행하는 대신 resolve를 실행합니다.

const printAll = () => {
    printString('a')
    .then(() => {
        return printString('b')
    })
    .then(() => {
        return printString('c')
    })
}
printAll();

위 함수는 a라는 string을 입력인자로 받고 Promise를 생성합니다.
이후 다음 작업으로 .then을 사용하는데, 이 결과로 위와 같이 b를 입력인자로 넣은 Promise를 리턴합니다.
callback과 동일하게 동작하지만, 더 가시성이 있다는 장점이 있습니다.

Promise.then으로 다음 작업을 이어나갈 수 있으며,
에러 핸들링은 reject를 실행하여 .catch로 관리할 수 있습니다.

Promise 또한 .then의 연속적인 사용으로 Promise hell을 유발할 수 있습니다.
이를 Promise 클래스를 미리 선언하고, 결과에 return 하는 방식으로 가시성을 향상할 수 있습니다.

1.3 Async / Await 📌

Promise를 조금 더 간편하게 사용하기 위해 ES7 부터 새롭게 도입된 Async가 있습니다.
Promise를 리턴하는 함수는 동일하며, 어떤 비동기 함수들을 실행할 때 동기적으로 실행하는 것처럼 보이는 코드를 작성할 수 있습니다.

비동기 함수를 실행할 때, 함수의 선언 전에 async를 앞에 붙이고,
.then을 수행하는 대신 앞에 await을 붙여 사용할 수 있습니다.

const printString = (string) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(string)
            resolve()
        },
                   1000)
    })
}
const printAll = async () => {
    await printString('a')
    await printString('b')
    await printString('c')
}

Promise와 유사하지만, await을 사용해 훨씬 가독성이 뛰어난 함수를 만들 수 있습니다.
중요한 점은, 함수의 선언 앞에 async를 꼭 붙여줘야 한다는 점, 그리고 Promise를 실행하는 단계마다 await로 구분해줘야 한다는 것입니다.


긴 글 읽어주셔서 감사합니다! 잘못된 부분이 있다면 언제든 댓글 달아주세요 ! 감사합니다! 👍

profile
즐겁다면 그것만으로 만만세!

30개의 댓글

comment-user-thumbnail
2021년 6월 25일

이런 아이디어 좋군요..

1개의 답글
comment-user-thumbnail
2021년 6월 26일

우연히 봤는데 신박한 비유네요! 😂
나중에 비동기 설명할 때 몰래 써봐야겠어요. 글 잘 읽었습니다 :)

1개의 답글
comment-user-thumbnail
2021년 6월 27일

이렇게 가슴이 웅장해지는 실생활의 예는 처음이네요 ㅋㅋ
비동기로직을 롤도 설명하시다니...놀랍습니다 :)

1개의 답글
comment-user-thumbnail
2021년 6월 27일

비동기적인 리신 콤보 ㅋㅋㅋㅋㅋ 바로 이해가 되는 글이네요 잘 읽다갑니다! 👍 👏🏻

1개의 답글
comment-user-thumbnail
2021년 6월 27일

제가 잘 이해되지 않아서 그러는데 혹시 비동기와 논블로킹의 차이점은 무엇인가요? 😄😄

6개의 답글
comment-user-thumbnail
2021년 6월 30일

이 포스팅을 누르지 않을 수 없었습니다 기가 막힌 설명에 박수를 보내고 갑니다 ㅎㅎㅎ

1개의 답글
comment-user-thumbnail
2021년 7월 3일

썸네일에 홀려서 들어왔는데, 정말 유익한 내용이었습니다!
친구에게 비동기 개념 설명해줄 때, 매우 찰진 비유가 될 것 같네요! 감사합니다.

1개의 답글
comment-user-thumbnail
2021년 7월 4일

가슴이 웅장해져서 사타구니가 축축해졌습니다.
가서 샤워를 하고 오겠습니다!

"그럼, 다시 수련에 정진하겠소."

1개의 답글
comment-user-thumbnail
2021년 7월 5일

와..완전 눈높이 교육이네요ㅋㅋㅋ 좋은 비유 들어주셔서 이해 잘됐습니다! 글 잘 읽고갑니다ㅎㅎ👍

1개의 답글

비동기 리신... 비유 너무 좋아서 보고 웃고 갑니다 ㅋㅋㅋㅋㅋㅋㅋ

1개의 답글