[Javascript] 콜백지옥, AJAX async/await 개선

이지연·2026년 1월 8일

개요

Ajax 요청(jQuery, Axios)은 기본적으로 비동기적으로 실행된다.
즉, “요청을 보낸 코드 줄”이 실행되고 나서 응답을 기다리는 동안에도 다음 코드가 계속 실행될 수 있다.

이 글은 다음 흐름으로 정리한다.

  • AJAX 요청이 왜 비동기인지, 실행 순서가 왜 뒤바뀌는지
  • “응답을 이용해 다음 요청을 보내야 할 때” 생기는 문제(콜백지옥)
  • 같은 요구사항을 async/await로 해결하는 방식

각 예제는 실행 순서를 콘솔 로그로 확인하도록 구성되어 있으니, 개발자도구(Console)를 열고 버튼 없이 바로 실행되는 출력 순서를 보는 방식으로 실습하면 된다.


개요


AJAX는 비동기적으로 실행된다

아래 코드는 jQuery.ajax로 데이터를 가져오는 가장 기본 형태다.

  • dataRequest()에서 AJAX 요청을 등록한다.
  • 호출 직후에는 아직 네트워크 응답이 오지 않았을 수 있으므로,
  • dataRequest() 아래의 console.log("실행순서 1")이 먼저 찍힐 수 있다.

코드 요약(핵심 흐름)

  • $(document).ready(() => { ... })
    DOM 객체가 완전히 로드된 이후에 콜백함수 실행 요청
  • $.ajax({ ..., success: (result) => { ... } })
    요청이 성공하면 success 콜백이 실행됨

실행 순서 포인트

dataRequest();
console.log("실행순서 1");

그리고 success 내부는 이렇게 구성돼 있다.

success: (result) => {
  // 비동기함수 내에서 결과작업은 동기적 실행순서 보장
  console.log("실행순서 2");
  console.log(result);
  console.log("실행순서 3");
}

정리하면:

  • 바깥쪽(동기 영역)에서 실행순서 1이 먼저 나올 수 있다.
  • 응답이 도착하면 success 콜백 내부에서 실행순서 2 → result → 실행순서 3그 블록 안에서 순서대로 실행된다.

콜백지옥(Callback Hell)

두 번째 예제는 제목 그대로 “콜백지옥” 상황을 코드로 보여준다.
페이지에는 callback_hell.png 이미지가 포함되어 있고, 중첩된 콜백 구조가 왜 문제가 되는지 시각적으로도 설명하려는 의도다.

왜 콜백이 중첩될까?

요구사항이 이렇게 바뀌는 순간이 문제의 시작이다.

  • “새로운 API 요청을 통해 받은 응답을 활용해 새로운 요청을 보내야 한다”
  • 그런데 ajax는 비동기이므로, 첫 번째 요청의 결과가 필요한 두 번째 요청은 반드시 첫 번째 success 안에서 실행해야 한다.

그래서 코드가 아래처럼 success 안에 또 ajax, 또 success 안에 또 ajax… 형태로 깊어지게 된다.

코드 흐름(출력 로그 기준)

  • dataRequest(); console.log("A");
  • 첫 번째 요청 성공 시: "B"
  • 두 번째 요청 성공 시: "C"
  • 세 번째 요청 성공 시: "D" + result 출력

즉, 화면(동기 영역)에서는 A가 먼저 찍히고, 요청이 끝나는 순서대로 B → C → D가 찍히는 형태가 된다.

콜백지옥의 문제점

이 구조는 동작은 하지만, 중첩이 깊어질수록 아래 문제가 커진다.

  • 들여쓰기가 계속 깊어져 가독성이 급격히 떨어짐
  • 에러 처리(error)가 각 단계마다 퍼져서 중복이 많아짐
  • “B 단계에서 실패했을 때”, “C 단계에서 실패했을 때” 같은 분기 처리가 복잡해짐
  • 로직 수정(중간 단계 추가/삭제)이 어려워짐

async/await로 개선하기

세 번째 예제는 같은 “연속 요청” 구조를 async/await로 평탄화(flatten)한 버전이다.

핵심 개념(코드 주석 그대로)

  • async: 함수 전체가 비동기임을 의미
  • await: 비동기함수 내에서 각 함수의 호출에 동기성을 보장하기 위한 키워드

코드 구조

요청 자체는 requestPost()로 분리했다.

const requestPost = () => {
  return $.ajax({
    url: "https://jsonplaceholder.typicode.com/posts/1",
    type: "GET",
    dataType: "json",
  });
};

그리고 dataRequestasync로 선언한 뒤, 각 요청 앞에 await를 붙여 순서를 보장한다.

const dataRequest = async () => {
  try {
    const result1 = await requestPost();
    console.log("B");
    console.log(result1);

    const result2 = await requestPost();
    console.log("C");
    console.log(result2);

    const result3 = await requestPost();
    console.log("D");
    console.log(result3);
  } catch (e) {
    console.log("error", e);
  }
};

마지막 실행은 DOM 로드 이후로 맞췄다.

$(document).ready(() => {
  dataRequest();
  console.log("A");
});

실행 순서 포인트

  • AdataRequest()를 호출한 직후 찍힌다.
  • B → C → D는 각각의 await가 끝난 뒤 순서대로 실행된다.
  • 에러는 try/catch로 한 곳에서 모아서 처리 가능하다.

정리: 3단계 흐름이 의미하는 것

  • AJAX 요청은 비동기이기 때문에 “요청 코드” 다음 줄이 먼저 실행될 수 있다.
  • 응답을 이용해 다음 요청을 보내야 하면 success 안으로 들어가게 되고, 그게 반복되면 콜백지옥이 된다.
  • async/await는 비동기 코드를 “순차 실행처럼 읽히게” 만들어서 중첩과 복잡도를 크게 줄인다.
profile
Eazy하게

0개의 댓글