동기적(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)라고 합니다. 일련의 순서대로 진행이 아닌, 효율적인 작업을 위해 어떤 작업을 실행시키고 그 작업이 실행되는 동안 다른 작업을 수행 할 수 있습니다.
비동기적 프로그래밍의 주요 사례는 아래와 같습니다.
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
이라는 함수를 만들고, 인자로 string
과 callback
함수를 받습니다.
이 함수는 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를 사용할 수 있습니다.
promise
는 callback chain
을 핸들링할 수 있는 하나의 Class
입니다.
promise
는 resolve
와 reject
로 함수를 실행하거나, 에러를 핸들링할 수 있습니다.
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
하는 방식으로 가시성을 향상할 수 있습니다.
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
로 구분해줘야 한다는 것입니다.
긴 글 읽어주셔서 감사합니다! 잘못된 부분이 있다면 언제든 댓글 달아주세요 ! 감사합니다! 👍
이런 아이디어 좋군요..