동기(synchronous)와 비동기(asynchronous)를 간단히 말씀드려보자면, 일차선 고속도로와 다차선 고속도로에 비유할 수 있는데요.
좀 더 자세히 말하자면 1차선 고속도로는 앞차의 속도에 따라 뒷차의 목적길 도착 시간도 그만큼 느려질수도 있고, 빨라질수도 있죠? 동기도 마찬가지로 하나의 코드 그룹을 순차적으로 실행하면서 각 코드의 실행 속도에 따라 전체 코드 그룹의 종료 시점도 달라지죠.
반면 다차선 고속도로는 1차선의 앞차가 아무리 늦게 가더라도 나머지 2,3차선에 영향을 끼치지 않죠? 비동기도 마찬가지로 각각의 코드 그룹이 개별적으로 실행되면서 그 그룹마다의 코드 그룹의 종료 시점도 각자마다 달라지는 것을 의미합니다.
동기, 비동기의 실행 속도를 보여주는 예시
물론 이러한 비동기와 동기도 각각의 장점과 단점이 있는데요, 대표적인 비동기의 장단점은 개별 코드마다의 실행 속도가 달라 실행 속도 측면에서는 동기보다 빠르나, 코드가 그만큼 분산되어 있으므로 가독성이 떨어질 수 있다는 단점이 존재하고,
반대로 동기는 코드의 직관성과 가독성이 높은 대신 비동기보다는 다소 느린 실행 속도를 가질 수 밖에 없다는 단점이 존재합니다.
동기, 비동기의 실행 프로세스에 대한 예시
또한 이러한 동기와 비동기 방식은 자바에서의 싱글 스레드와 멀티 스레드를 생각해 보시면 좀 더 이해하시기 편하시리라 생각됩니다.
자바스크립트에도 대표적인 비동기 메서드는 fetch, Promise, async/await 등의 API나 객체, 키워드들이 존재하지만 JS에서의 비동기 메서드를 우선적으로 이해하기 위한 메서드로는 setTimeout 메서드가 단연 제격인데요.
이 메서드는 특정 시간을 설정하면 해당 시간이 지난 뒤에 사용자가 정의한 코드나 함수를 실행하게 하는 메서드입니다. 글로는 이해가 안가실테니 간단한 코드를 한 번 살펴보도록 하죠.
let hello = setTimeout(() => { window.alert("안녕하세요.") }, 5000); hello();
위의 코드는 setTimeout 메서드로 5초(5000)가 지난 뒤 화면에(window) 알림창(alert)으로 "안녕하세요" 문구를 출력하게 하는 메서드입니다. 실제로 화면을 키면 5초 후에 해당 알림창이 다음과 같이 뜨는데요.
이처럼 setTimeout 메서드는 기본적으로 두 개의 매개변수, 그러니까 실행할 함수와 시간 단위를 밀리초 단위(1초는 1000)로 전달하여 사용합니다.
setTimeout(function(){}, 1000)
이러한 setTimeout은 기본적으로 익명 함수이므로 함수 표현식으로, 그러니까 익명 함수를 변수에 저장해서 사용을 하게되죠. 왜냐면 함수 표현식으로 사용해야 해당 setTimeout을 멈추거나 종료하거나, 호출할 수 있으니까요.
이러한 setTimeout 메서드가 비동기를 이해하기 쉬운 이유를 지금부터 말씀드릴텐데요. 아래의 코드는 setTimeout 메서드를 세 개 호출한 메서드들입니다. 코드상으로만보면 첫번째 코드가 실행되고, 그 다음 두번째, 세번째 코드가 실행될 것 같죠?
하지만 실행 속도가 가장 짧은 맨 마지막의 메서드가 첫번째로, 그 위의 메서드가 두번째, 마지막 맨 위의 메서드가 세 번째로 실행되죠.
setTimeout(function(){ console.log("꾸에에엑") }, 5000); setTimeout(function(){ console.log("왈왈와르를왈") }, 3000); setTimeout(function(){ console.log("미야오우오웅") }, 1000);
이러한 setTimeout 메서드는 대표적인 비동기 메서드로서 정말 많이 이용되는데요. 특히 이러한 setTimeout 메서드를 특정 시점에 강제 종료하게 하는 메서드 또한 존재하는데 바로 clearTimeout메서드 입니다.
맨 위의 코드 예시를 보면, 해당 hello 메서드는 5초 후에 알림창을 출력하는데요.
let hello = setTimeout(() => { window.alert("안녕하세요.") }, 5000); hello();
이때 화면 상에 버튼을 하나 만들고, 그 버튼을 누르면 setTimeout을 강제로 종료하게 하려면 어떻게 해야할까요? 네. 이 때 바로 clearTimeout 메서드를 사용하면 됩니다.
- html 코드
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="arrow_index.js" defer></script> <title>Document</title> </head> <body> <button id = "interval_btn">알람 그만!</button> </body> </html>
- JS 코드
let hello = setTimeout(() => { console.log("안녕하세요."); }, 5000); const btn = document.querySelector("#interval_btn"); btn.addEventListener('click', () => { clearTimeout(hello); alert("알람이 해제되었습니다."); });
위의 코드는 5초 후에 실행되는 setTimeout 메서드를 도중에 종료하도록 HTML 상의 버튼에 이벤트로 clearTimeout 메서드를 호출하고, 그 증거로 alert 메서드를 또 호출하도록 하는 코드인데요. 해당 코드를 실행해 해당 콘솔에 "안녕하세요"가 5초 뒤 나타나기 전에 버튼을 누르면 다음과 같이 정상적으로 setTimeout 메서드가 종료되는걸 확인할 수 있습니다.
이러한 setTimeout 메서드는 특정 시간이 지나면 딱 한 번 메서드를 호출하는데요. 그래서 해당 메서드를 재귀 함수 형식으로 해당 함수를 일정한 시간마다 반복하게 할 수 있기도 한데, 이러한 역할을 하는 메서드는 이미 존재합니다. 바로 setInterval 형식인데요.
사용하는 방법은 똑같습니다. 그냥 함수와 특정 시간을 매개변수로 전달하면 되는데, 특히 매개변수로 시간을 전달하면 그 시간을 반복해서 해당 함수를 실행하게 됩니다. 이름에서도 유추(interval)해볼수 있는 내용이죠?
물론 해당 setInterval 메서드를 멈추게 하는 메서드인 clearInterval 메서드도 존재하고요.
그럼 아래의 예시 코드를 살펴보겠습니다.
- 10초가 되기 전까지 setInterval 메서드를 이용해 1초마다 cnt를 출력하다가 10초가 되면 알람을 표시하고 해당 setInterval 메서드를 종료하는 clearInterval 메서드를 정의.
let cnt = 1 let cntPlus = setInterval(()=>{ console.log(cnt++) if(cnt > 10){ alert("10초 경과"); clearInterval(cntPlus) } }, 1000);
위에서 살펴본 setTimeout메서드와 clearTimeout, setInterval과 clearInterval 메서드를 이용해 간단한 시계 화면을 출력하는 부분을 구현해 보겠습니다. 아래는 틀이 되는 HTML 코드로, 각각 시간을 표시하는 h1태그와 현재 시간을 출력하고, 중지시키고, 초기화 하는 버튼으로 세 개를 만들어 줍니다.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="./practice_index.js" defer></script> <title>Document</title> </head> <body> <h1 id ="clock">0000-00-00 00:00:00</h1> <button id = "run" onclick="run()">현재 시간 출력</button> <button id = "stop" onclick="stop()">중지</button> <button id = "init" onclick="init()">초기화</button> </body> </html>
그 다음 화면에 표시해줄 날짜와 시간대를 watch 메서드를 이용해 만들어주는데요. 이때 Date 내장 객체와 약간의 DOM 요소를 활용해 만들어 줍니다.
// 10 미만인 시간대는 앞에 0을 붙여주는 메서드 function addZero(num){ return num < 10 ? "0" + num : num; } // 화면에 시간을 표시하는 메서드 const watch = () => { let fullDate = new Date(); let year = fullDate.getFullYear(); let month =addZero(fullDate.getMonth()); let date = addZero(fullDate.getDate()); let hour = addZero(fullDate.getHours()); let minutes = addZero(fullDate.getMinutes()); let seconds = addZero(fullDate.getSeconds()); let resultDate = `${y}-${M}-${d} ${h}:${m}:${s}`; document.getElementById("clock").innerHTML = resultDate; }
그리고 사용자가 첫번째 버튼인 현재 시간 출력을 누르면 해당 watch메서드를 1초마다 실행시킬 setInterval 메서드를 정의해 줍니다.
// 해당 setInterval 메서드를 전역에서 사용하기 위해 바깥에 선언 let control; const run = () => { control = setInterval(watch, 1000); };
그 다음, 사용자가 두번째 버튼인 중지 버튼을 누르면 해당 watch메서드를 즉각 종료시키는 clearInterval 메서드를 정의해 줍니다.
const stop = () => { clearInterval(control); };
마지막으로 사용자가 세번째 버튼인 초기화 버튼을 누르면 해당 watch메서드를 즉각 종료시키고, 화면에 표시되어 있는 시간도 전부 0으로 초기화 해주도록 메서드를 정의해 줍니다.
const init = () => { clearInterval(control); document.getElementById("clock").innerHTML = `0000-00-00 00:00:00`; };
자, 이제 한 번 버튼들을 눌러볼까요? 버튼들을 누르면 다음과 같이 동작됩니다.