자바스크립트 비동기 기초 - JavaScript 시리즈 #8

자바스크립트 비동기 기초

지금까지 변수와 데이터 타입, 조건문과 반복문, 함수, 배열, 객체, 이벤트를 배웠다면, 이번엔 비동기 프로그래밍을 배워볼 거다. 비동기는 자바스크립트에서 시간이 걸리는 작업(예: 서버 요청, 파일 읽기)을 기다리지 않고 동시에 처리할 수 있게 해주는 핵심 개념으로, 현대 웹 개발에서 필수적이다. 비동기를 잘 이해하면 느린 작업에도 반응 빠른 애플리케이션을 만들 수 있다.


내가 처음 코딩을 배울 때 서버에서 데이터를 가져오는데 페이지가 멈추는 경험을 했는데, 비동기를 배우고 나니까 이런 문제를 해결할 수 있었다. 이번 글에선 비동기의 정의와 필요성, 콜백 함수, 프로미스, async/await 사용법, 그리고 실습 예제를 상세히 다룰 거다. 천천히 따라오면서 콘솔이나 HTML 파일에서 실행해보면 비동기의 매력을 느낄 수 있을 거다. 비동기는 자바스크립트가 단일 스레드 언어임에도 불구하고 멀티태스킹처럼 동작하게 해주는 강력한 도구다.


앞서 배운 개념들과 비동기를 결합하면 더 실용적인 코드를 작성할 수 있다. 예를 들어, 이벤트로 버튼 클릭을 감지하고, 비동기로 서버에서 데이터를 가져와서 객체에 저장한 뒤, 배열로 화면에 표시하는 작업을 쉽게 할 수 있다.

Async Concept

비동기란 무엇인가?

비동기(Asynchronous)는 코드가 순차적으로 실행되지 않고, 시간이 걸리는 작업을 기다리지 않고 다음 작업을 먼저 처리하는 방식이다. 자바스크립트는 기본적으로 단일 스레드(Single-Threaded) 언어로, 한 번에 한 가지 작업만 처리한다. 예를 들어, 서버에서 데이터를 가져오는 데 5초가 걸린다면 동기(Synchronous) 방식으론 그 5초 동안 아무것도 못 하고 기다려야 한다. 하지만 비동기 방식은 데이터를 요청한 뒤 기다리는 동안 다른 코드를 실행할 수 있다.

예를 들어, 동기 방식은 이렇게 동작한다:

console.log("시작");
function slowTask() {
    let start = Date.now();
    while (Date.now() - start < 3000) {} // 3초 대기
    console.log("느린 작업 완료");
}
slowTask();
console.log("끝");
// 출력: "시작" -> 3초 후 -> "느린 작업 완료" -> "끝"
        

위 코드는 3초 동안 멈춘 뒤 다음 줄로 넘어간다. 내가 처음 이걸 봤을 때 답답했는데, 비동기는 이런 문제를 해결한다:

console.log("시작");
setTimeout(function() {
    console.log("느린 작업 완료");
}, 3000);
console.log("끝");
// 출력: "시작" -> "끝" -> 3초 후 -> "느린 작업 완료"
        

setTimeout은 비동기 함수로, 3초 후에 실행할 코드를 예약하고 바로 다음 줄로 넘어간다. 내가 처음 비동기를 접했을 때 코드가 순서대로 안 돼서 혼란스러웠지만, 나중에 보니 이게 자바스크립트의 강점이었다.

비동기는 서버 요청, 파일 읽기, 타이머 같은 시간이 걸리는 작업에서 필수다. 실무에선 사용자 인터페이스가 멈추지 않게 하고, 데이터를 백그라운드에서 가져오거나, 여러 작업을 병렬로 처리할 때 많이 쓴다. 자바스크립트의 비동기는 이벤트 루프(Event Loop)라는 메커니즘으로 동작하는데, 이는 나중에 더 깊이 다룰 예정이다.

Async Flow

콜백 함수로 비동기 처리

비동기를 처리하는 가장 기본적인 방법은 콜백 함수(Callback Function)다. 콜백은 작업이 끝난 후 실행할 함수를 전달하는 방식이다.

기본 예제

console.log("시작");
setTimeout(function() {
    console.log("3초 후 실행");
}, 3000);
console.log("끝");
// 출력: "시작" -> "끝" -> 3초 후 -> "3초 후 실행"
        

setTimeout에 전달된 함수가 콜백이다. 내가 처음 콜백을 썼을 때 타이밍이 신기했는데, 비동기의 기본을 이해하는 데 큰 도움이 됐다.

중첩 콜백 문제

콜백을 여러 번 쓰면 코드가 복잡해진다(Callback Hell):

setTimeout(function() {
    console.log("첫 번째 작업");
    setTimeout(function() {
        console.log("두 번째 작업");
        setTimeout(function() {
            console.log("세 번째 작업");
        }, 1000);
    }, 1000);
}, 1000);
        

내가 처음 이걸 봤을 때 들여쓰기가 너무 깊어져서 읽기 힘들었다. 이를 해결하기 위해 나중에 프로미스와 async/await가 나왔다.

프로미스로 비동기 개선

프로미스(Promise)는 ES6에서 도입된 비동기 처리 방식으로, 콜백 헬을 피하고 코드를 더 깔끔하게 만든다. 프로미스는 작업의 상태를 나타내는 객체다.

프로미스 상태

- Pending: 작업 진행 중.

- Fulfilled: 작업 성공.

- Rejected: 작업 실패.

기본 사용

let promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve("성공!");
    }, 2000);
});
promise.then(function(result) {
    console.log(result); // 2초 후 "성공!"
});
        

resolve는 성공 시 호출, reject는 실패 시 호출한다. 내가 처음 프로미스를 썼을 때 체이닝이 가능하다는 점이 인상 깊었다:

promise
    .then(function(result) {
        console.log(result);
        return "다음 단계";
    })
    .then(function(next) {
        console.log(next);
    })
    .catch(function(error) {
        console.log("에러: ", error);
    });
// 출력: "성공!" -> "다음 단계"
        

catch로 에러를 잡을 수 있어서 안정적이다.

async/await로 더 간결하게

async/await는 ES2017에서 추가된 비동기 방식으로, 프로미스를 더 직관적으로 쓸 수 있게 한다.

기본 사용

async function fetchData() {
    let promise = new Promise((resolve) => {
        setTimeout(() => resolve("데이터 도착!"), 2000);
    });
    let result = await promise;
    console.log(result);
}
fetchData(); // 2초 후 "데이터 도착!"
        

async 함수 안에서 await를 쓰면 프로미스가 완료될 때까지 기다린다. 내가 처음 이걸 썼을 때 동기 코드처럼 보이는데 비동기라 놀랐다.

에러 처리

async function fetchData() {
    try {
        let promise = new Promise((resolve, reject) => {
            setTimeout(() => reject("에러 발생!"), 2000);
        });
        let result = await promise;
        console.log(result);
    } catch (error) {
        console.log("에러: ", error);
    }
}
fetchData(); // 2초 후 "에러: 에러 발생!"
        

try-catch로 깔끔하게 에러를 잡는다.

Async Await Example

실습 예제

비동기를 활용한 실습을 해보자:

<button id="fetchButton">데이터 가져오기</button>
<div id="result"></div>
<script>
    function fetchData() {
        return new Promise((resolve) => {
            setTimeout(() => resolve("서버에서 온 데이터"), 2000);
        });
    }

    async function loadData() {
        let button = document.getElementById("fetchButton");
        let resultDiv = document.getElementById("result");
        button.addEventListener("click", async () => {
            resultDiv.textContent = "로딩 중...";
            try {
                let data = await fetchData();
                resultDiv.textContent = data;
            } catch (error) {
                resultDiv.textContent = "에러 발생!";
            }
        });
    }
    loadData();
</script>

버튼 클릭 시 2초 후 데이터를 표시한다. 이벤트와 비동기를 결합한 예제다.

 

주의할 점

콜백 중첩은 피하고, 프로미스나 async/await를 쓰자. 비동기 작업은 순서를 잘 관리해야 한다. 에러 처리를 반드시 추가하자.

결론

이번 포스팅에서 자바스크립트 비동기의 기초를 다뤘다. 콜백, 프로미스, async/await를 배웠으니 이제 시간이 걸리는 작업도 효율적으로 처리할 수 있다. 다음 글에선 비동기의 이벤트 루프와 고급 활용법을 알아보자.

+ Recent posts