1. 동기 비동기
개발을 하다 보면 동기(Synchronous)와 비동기(Asynchronous)라는 개념을 굉장히 자주 마주치게 됩니다.
동기(Synchronous)란, 요청한 작업이 완료될 때까지 다음 작업이 대기하는 방식을 의미합니다.
쉽게 말해, 하나의 작업이 끝나야만 다음 작업이 시작되는 방식이죠. 이 방식은 순서가 보장되지만, 작업이 오래 걸릴 경우 전체 흐름이 멈춰버릴 수 있습니다.
반대로, 비동기(Asynchronous)는 작업을 요청한 뒤, 그 결과를 기다리지 않고 다음 작업을 바로 실행하는 방식을 말합니다. 결과가 준비되면 나중에 통보를 받아 처리하기 때문에 긴 작업이 있어도 프로그램 전체가 멈추지 않고 계속 진행될 수 있죠.
다른 분야에서도 마찬가지지만, 특히 프론트 개발을 진행하다 보면 동기 처리 방식이 굉장히 비효율적이라는 점을 많이 느끼실 수 있을 겁니다.
자바스크립트기 비동기 처리를 염두에 두고 설계된 언어이기도 하고, 사용자의 요청을 동기 방식대로 하나하나 차례대로 수행하다 보면 사용자 입장에서는 서비스가 매우 느리고 답답하게 느껴질 수 있습니다.
그렇기에 프론트엔드 개발에서는 비동기 처리를 적극적으로 활용하는 것이 필수적입니다.
2. 비동기 예시
실제로 비동기 처리가 사용되는 대표적인 예시 두 가지를 살펴보겠습니다.
물론 이는, 자바 스크립트 기준입니다!
2.1 Ajax
Ajax는 페이지 전체를 새로 고치지 않고 서버와 통신할 수 있게 해주는 기술입니다.
과거에는 웹 페이지에서 데이터를 갱신하려면 전체 페이지를 다시 불러와야 했지만, Ajax 덕분에 필요한 데이터만 요청하고 갱신할 수 있게 되었습니다.
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.onload = function() {
if (xhr.status === 200) {
console.log('데이터:', xhr.responseText);
} else {
console.error('에러 발생');
}
};
xhr.send();
예시 코드를 하나 가져와보았습니다.
XMLHttpRequest 객체를 이용해 서버에 비동기 요청을 보내고, 결과가 오면 onload 핸들러를 통해 처리합니다.
이 과정을 통해 사용자는 화면을 새로고침하지 않고도 데이터를 받아볼 수 있는 것이죠.
Ajax는 웹의 사용자 경험을 혁신적으로 바꾼 대표적인 비동기 기술이라고 볼 수 있겠습니다.
2.2 setTimeout()
두 번째로 볼 것은 setTimeout()입니다.
이는 특정 시간이 지난 후 함수를 실행하도록 예약하는 기능입니다.
특정 작업을 나중에 처리하고 싶을 때 유용하게 사용할 수 있죠.
console.log('A 작업 시작');
setTimeout(() => {
console.log('B 작업 (2초 후 실행)');
}, 2000);
console.log('C 작업 계속 진행');
이 기능은 아마 정말 많이 보셨고, 사용해 보셨을 겁니다. -아니면 저만..... 많이 사용해 봤을까요...-
만약 비동기가 아니어서 저 setTimeout이 없이
console.log('A 작업 시작');
console.log('B 작업 (2초 후 실행)');
console.log('C 작업 계속 진행');
이런 식의 코드였다면 아무런 변화 없이
A 작업 시작
B 작업 (2초 후 실행)
C 작업 계속 진행
의 순서대로 진행되었을 겁니다.
다만 저희는 setTimeout의 비동기 기능이 있어
A 작업 시작
C 작업 계속 진행
B 작업 (2초 후 실행)
이런 출력 값이 출력되게 됩니다.
console.log('B 작업 (2초 후 실행)');이라는 콘솔값이 setTimeout으로 인해 2초 뒤에 실행되기 때문에
그동안 다른 작업(C)이 먼저 진행되게 됩니다.
이처럼 setTimeout은 동작을 비동기적으로 지연시켜 프로그램 흐름이 막히는 경우를 방지할 수 있습니다.
3. 콜백으로 문제 해결하기
비동기 작업을 수행할 때, 작업이 끝난 이후에 무엇을 할 것인가 를 지정해 줄 필요가 있습니다.
그리고 바로 이때 사용하는 것이 콜백 함수(Callback function)입니다.
콜백함수란, 다른 함수의 인자로 전달되어, 특정 시점에 호출되는 함수를 의미합니다.
특히 비동기 처리의 경우 작업 완료 후 실행할 함수를 콜백으로 넘겨줘서 순서를 제어합니다.
function fetchData(callback) {
setTimeout(() => {
console.log('서버에서 데이터 가져오기 완료');
callback('받은 데이터');
}, 2000);
}
function displayData(data) {
console.log('받은 데이터:', data);
}
fetchData(displayData);
fetchData 함수는 2초 후 데이터를 받아온 뒤, 콜백 함수인 displayData를 호출해서 받은 데이터를 넘겨줍니다.
그렇기에 비동기 작업이 끝나고 원하는 처리를 이어서 할 수 있습니다.
위 코드의 출력 결과를 생각해 보면
(2초 대기)
서버에서 데이터 가져오기 완료
받은 데이터: 받은 데이터
위와 같이 2초 대기 후에 console.log('서버에서 데이터 가져오기 완료');를 실행한 뒤,
콜백에 의해 console.log('받은 데이터:', data);가 실행되게 됩니다.
콜백 지옥
물론 이 콜백이 모든 일의 해결책은 아닙니다.
비동기 작업이 끝난 후 일을 이어서 처리할 수는 있게 되었지만, 콜백을 남용하면 코드가 점점 복잡해지는 문제가 발생합니다.
그리고 이를 흔히 콜백 지옥이라고 부르고는 합니다.
간단한 예시만 봐도 어떤 문제인지는 아실 수 있을 겁니다.
loginUser('user', 'password', function(user) {
getUserInfo(user.id, function(info) {
getPosts(info.name, function(posts) {
getComments(posts[0].id, function(comments) {
console.log('모든 데이터 처리 완료');
});
});
});
});
4. promise!
Promise는 비동기 작업을 보다 깔끔하고 관리하기 쉽게 만들어주는 자바스크립트 객체입니다.
콜백 지옥을 피하고, 비동기 작업의 흐름을 명확하게 제어할 수 있도록 설계되었습니다.
Promise는 말 그대로 "약속"입니다.
비동기 작업이 성공하면 결과를 주고, 실패하면 이유를 알려줍니다.
그렇기에 Promise 객체는 3가지 상태를 가집니다.
상태 | 설명 |
pending | 작업이 아직 완료되지 않은 상태 |
fulfilled | 작업이 성공적으로 완료된 상태 |
rejected | 작업이 실패한 상태 |
const promise = new Promise((resolve, reject) => {
const success = true; // 예시로 성공 여부를 결정
if (success) {
resolve('성공 결과');
} else {
reject('실패 이유');
}
});
promise
.then(result => {
console.log('성공:', result);
})
.catch(error => {
console.error('실패:', error);
});
Promise의 예시를 가져와보았습니다.
위 코드에서 resolve()가 호출되면 then()으로 이어지고, reject()가 호출되면 catch()로 에러를 처리합니다.
이를 사용해서, 아까 콜백 지옥에서 보여주었던 코드를 Promise를 사용해 다시 구현해 보겠습니다.
loginUser('user', 'password')
.then(user => getUserInfo(user.id))
.then(info => getPosts(info.name))
.then(posts => getComments(posts[0].id))
.then(comments => {
console.log('모든 데이터 처리 완료');
})
.catch(error => {
console.error('에러 발생:', error);
});
. then()을 이용해서 순서를 깔끔하게 표현할 수 있고, catch()를 사용해 모든 에러를 처리할 수 있어 관리가 쉬워지게 됩니다.
요약
자바스크립트를 이용하는 웹 개발에서는 비동기 처리가 필수적입니다.
비동기란, 요청한 작업이 끝날 때까지 기다리지 않고 다른 작업을 계속 진행하는 방식을 의미합니다.
덕분에 사용자는 프로그램이 멈추지 않고 빠르게 반응하는 것처럼 느낄 수 있죠.
프론트엔드 개발에서는 서버에서 데이터를 받아오거나, 타이머 이벤트를 기다리는 등 다양한 상황에서 비동기 처리가 필요합니다. 만약 모든 작업을 동기적으로 처리한다면, 서버 응답을 기다리는 동안 화면이 멈추고 사용자 경험이 크게 저하될 수 있기 때문이죠.
이를 해결하기 위해 가장 기본적으로 사용되는 방법이 콜백 함수입니다.
콜백은 "이 작업이 끝나면 이 함수를 실행해 줘"라고 지시하는 구조로, 비동기 작업이 끝나고 나서 후속 작업을 이어나갈 수 있게 해 줍니다.
하지만 콜백을 계속 사용하다 보면 콜백 안에 콜백, 또 그 안에 콜백이 중첩되어 코드가 복잡해지는 콜백 지옥(callback hell)에 있기에, 이를 해결하기 위해 등장한 것이 Promise입니다.
Promise는 비동기 작업의 성공(resolve)이나 실패(reject)를 명확하게 표현하고,. then()과. catch()를 이용해 코드를 깔끔하게 순차적으로 이어갈 수 있게 해주는 기능입니다.
결국 비동기란,
"사용자가 느끼는 부드럽고 빠른 서비스 경험을 위해, 작업 완료를 기다리지 않고 효율적으로 프로그램을 실행하는 것"이며,
이를 제대로 다루기 위해 콜백 → Promise와 같은 기술들이 발전해왔다.
라고 요약할 수 있습니다.
출처
'Language > JavaScript' 카테고리의 다른 글
호이스팅(Hoisting) 이해하기 (0) | 2025.04.08 |
---|---|
자바스크립트의 모듈 : 순환 참조 (0) | 2025.03.12 |
모듈 시스템 [CommonJS, AMD, UMD, ESM] (0) | 2025.03.10 |
나는 DOM이 무엇인지 알고 사용하고 있었을까? (0) | 2025.02.02 |