해당 포스트는 AJAX 기술에 대해 학습하던 중에 아래 개념을 더 확실하게 하기 위해 작성했다
-> 동기/ 비동기에 대한 확실한 이해
-> 비동기호출의 콜백 지옥 예시
2025.04.08 - [Frontend/JavaScript] - Fetch API 란 ? AJAX 기술 이해하기
# 동기와 비동기 개념
구분 | 개념 |
동기(Synchronous) | 어떤 작업을 끝날 때까지 작업 안하고 기다림 |
비동기(Asynchronous) | 어떤 작업이 끝날 때까지 기다리지 않고 바로 실행함 |
동기 호출
console.log('1');
const res = $.ajax({
url: 'https://...',
async: false
});
console.log('2'); // 요청 끝날 때까지 아예 안 찍힘
동기호출은 응답을 보내고 응답이 올때까지 기다렸다가 다음수행할 것들을 처리하는데, 몇가지 문제점이 있기 때문에 거의 사용하지 않는다.
동기 호출을 쓰지않는 이유
1. 브라우저 멈춤 ( UI 프리징 )
- 요청하고 응답을 기다리는 동안 클릭도 안되고 페이지 전체가 얼어버리기 때문에 UX(사용자 경험) 최악
2. 메인 스레드 블로킹
- 자바스크립트는 싱글 스레드인데 동기 호출을 하는 순간 메인 스레드를 막아버려서 그 사이에 렌더링이나 이벤트 처리를 막아버린다. ( 그 때문에 브라우저 멈춤 현상 발생 )
3. 성능 저하 및 예측 어려움
- 페이지가 뭠췄을 때 느린 네트워크 환경이라면 더더욱 브라우저가 느리게 실행된다
- 동기 호출을 사용하면 코드 실행 순서를 명확히 제어하기 어렵고, 디버깅도 헷갈릴 수 있다.
동기호출 옵션은 XMLHttpRequest 에서만 지원한다
fetch()나 axios는 Promise 기반임
Promise는 원래 비동기 설계 철학 위에서 돌아감
그래서 async: false 같은 옵션 자체가 없음! ( 해도 무시됨 )
비동기 호출
# 예시 1)
<script>
console.log('1. 요청 시작');
$.ajax({
url: 'https://jsonplaceholder.typicode.com/posts/1',
method: 'GET',
success: function(data) {
console.log('3. 응답 받음:', data);
}
});
console.log('2. 요청 이후 코드 실행');
</script>
실행 순서
1. 요청 시작
2. 요청 이후 코드 실행
3. 응답 받음: {...}
다음과 같이 비동기 호출을 하게 되면 요청후 응답을 기다리지않고 바로 다음 코드를 실행한 후 콜백이 오면 그에 대해 응답한다.
이건 어떤 상황에서 문제가 있을까 ?
# 예시 )
function getUser() {
$.ajax({
url:'https://jsonplaceholder.typicode.com/users/1',
method: 'GET',
success: function(user) {
console.log('유저 정보 가져오기 성공 : ', user);
},
error: function() {
console.error('요청 실패');
}
});
}
function doSomthingUsingUserInfo() {
console.log('가져온 유저 정보로 처리할 작업');
}
getUser();
doSomthingUsingUserInfo();
코드 실행 순서로만 보면 실행 순서가 getUser() -> doSomthingUsingUserInfo() 로 정상적으로 이루어질 것 같다.
예상 콘솔 로그 ) 유저 정보 가져오기 성공 ~ -> 가져온 유저 정보로 처리할 작업
실제 콘솔 로그 ) 가져온 유저 정보로 처리할 작업 -> 유저 정보 가져오기 성공 ~
이는 비동기 요청의 특징 때문이다 .
비동기호출을 순서대로 실행하기 위해 Promise 객체를 활용하여 순서대로 시행되는 것처럼 할 수 있다.
function getUserPromise() {
return new Promise((resolve, reject) => {
$.ajax({
url: 'https://jsonplaceholder.typicode.com/users/1',
methode: 'GET',
success: resolve,
error: reject
});
});
}
function doSomthingUsingUserInfo() {
console.log('가져온 유저 정보로 처리할 작업');
}
getUserPromise()
.then(uesr => {
console.log('유저: ',user);
console.log('이름: ',user.name);
doSomthingUsingUserInfo();
})
.catch(err => {
console.error('에러: ', err);
});
이렇게 순서를 보장하고 싶으면 .then() 안에 순서대로 적어줘야 한다.
비동기 호출이 순서대로 실행되면 그게 동기호출 아닌가.. ?
=> 아니다! 순서대로 실행되는 것 처럼 보이게 할 뿐이다 !
# async/await 을 사용한 비동기 호출 예시
async function fetchData() { console.log('1'); const res = await fetch('https://...'); const data = await res.json(); console.log('2'); } fetchData(); console.log('3'); // 먼저 실행됨!
자바스크립트는 싱글 스레드 언어기 때문에 한 번에 하나의 작업만 실행한다.
await 는 기다리지만, 전체 앱은 멈추지 않은 상태에서 기다리는 것이다.
반면, 동기 호출시에는 실제로 앱이 멈춘상태에서 진행된다.
비동기 호출의 콜백 지옥
- 비동기 + 중첩 콜백의 조합에서 생기는 문제
- 콜백 안에 콜백을 넣는 구조 (중첩이 계속 반복됨)
# 예시
<script>
$.ajax({
url: 'https://jsonplaceholder.typicode.com/users/1',
method: 'GET',
success: function (user) {
console.log('1. 유저 정보:', user);
$.ajax({
url: 'https://jsonplaceholder.typicode.com/posts?userId=' + user.id,
method: 'GET',
success: function (posts) {
console.log('2. 글 목록:', posts);
$.ajax({
url: 'https://jsonplaceholder.typicode.com/comments?postId=' + posts[0].id,
method: 'GET',
success: function (comments) {
console.log('3. 첫 글 댓글:', comments);
},
error: function () {
console.error('댓글 가져오기 실패');
}
});
},
error: function () {
console.error('글 목록 가져오기 실패');
}
});
},
error: function () {
console.error('유저 정보 가져오기 실패');
}
});
</script>
예시에서 단계 1에서 success 콜백시에 단계2를 수행하는데, 단계 2에서는 단계 3의 콜백을 받고 있다. ( 중첩 계속 반복 )
# 문제점
- 들여쓰기 헬
- 각각의 error도 따로 있어서 복잡도 상승
- 위의 이유로 로직 수정/유지보수 어려움 ( 콜백 지옥이라고 부르는 이유 )
- Promise 가 없을 때, XHR 이나 $.ajax() 같은 비동기 작업을 순차적으로 처리하는 과정에서 발생했음
XMLHttpRequest 사용하는 것이 콜백지옥의 원인?
-> ❌
=> XMLHttpRequest 을 중첩으로 사용해서 발생하는 것 !
# 해결
1. Promise 버전으로 리팩토링 하기
function getUser(userId) {
return $.ajax({
url: 'https://jsonplaceholder.typicode.com/users/' + userId,
method: 'GET'
});
}
function getPost(userId) {
return $.ajax({
url: 'https://jsonplaceholder.typicode.com/posts?userId=' + userId,
method: 'GET'
});
}
function getComment(postId) {
return $.ajax({
url: 'https://jsonplaceholder.typicode.com/comments?postId=' + postId,
method: 'GET'
});
}
// 체이닝
getUser(1)
.then(user => {
console.log('1. 유저 정보:', user);
return getPost(user.id);
})
.then(posts => {
console.log('2. 글 목록:', posts);
return getComment(posts[0].id);
})
.then(comments => {
console.log('3. 첫 글 댓글:', comments);
})
.catch(err => {
console.error('에러 발생:', err);
});
체이닝(Chaining)이란?
함수를 호출한 결과로 또 다른 함수를 계속 이어붙이는 방식임.
특히 Promise에서 .then()을 계속 이어붙이는 걸 체이닝이라고 많이 말함
2. async/await 버전
코드가 더 간결하고 요즘 방식임
async function loadUserData() {
try {
const user = await getUser(1);
console.log('1. 유저 정보:', user);
const posts = await getPost(user.id);
console.log('2. 글 목록:', posts);
const comments = await getComment(posts[0].id);
console.log('3. 첫 글 댓글:', comments);
} catch (err) {
console.error('에러 발생:', err);
}
}
loadUserData();
그동안 주로 jQuery 로 프로젝트를 진행하며 $.ajax() 에만 익숙해졌었는데 이번에 Raect,Vue 를 공부하며 Fetch() API 사용에 익숙해지기 위해 이것 저것 정리를 해봤다.
나도 이제 요즘 개발자 되야지 .. ㅋ.ㅋ. ~~~
'Frontend > JavaScript' 카테고리의 다른 글
Fetch API 란 ? AJAX 기술 이해하기 (0) | 2025.04.08 |
---|---|
attr() 과 prop() 의 차이 (0) | 2024.03.18 |