Callback vs. Promise vs. Async/Await

JavaScript의 비동기 처리 방식은 프로그램의 실행 흐름을 막지 않고 다른 작업을 수행할 수 있게 해주는 중요한 기능이다.
주요 비동기 처리 방식에는 콜백(Callbacks), 프로미스(Promises), 그리고 async/await가 있다.

특성콜백 (Callback)PromiseAsync/Await
정의다른 함수의 인자로 전달되어 특정 시점에 실행되는 함수비동기 작업의 최종 완료 또는 실패를 나타내는 객체Promise를 기반으로 비동기 코드를 동기 코드처럼 작성할 수 있게 해주는 문법
도입 시기JavaScript 초기부터 사용ES6 (2015)ES8 (2017)
문법function(err, result) { … }new Promise((resolve, reject) => { … })async function() { await … }
에러 처리콜백 함수의 첫 번째 인자로 에러 객체 전달.catch() 메서드 사용try-catch 구문 사용
장점- 간단한 비동기 처리에 적합
- 모든 환경에서 지원
- 체이닝 가능
- 에러 처리 용이
- 병렬 처리 가능 (Promise.all)
- 동기 코드와 유사한 구조
- 가독성 향상
- 직관적인 에러 처리
단점- 콜백 지옥 발생 가능
- 에러 처리 복잡
- 약간의 학습 곡선 존재
- 브라우저 지원 고려 필요
- 항상 Promise를 반환
- 오래된 환경에서 지원 안 됨
비동기 처리 방식콜백 함수를 통해 결과 처리then() 메서드를 통해 결과 처리await 키워드로 결과를 기다림
중첩 처리콜백 안에 콜백을 계속 넣어야 함.then() 체이닝으로 처리일반적인 동기 코드처럼 작성 가능
병렬 처리복잡한 로직 필요Promise.all() 사용Promise.all()과 함께 사용
순차적 처리콜백 중첩으로 처리.then() 체이닝으로 처리일반 동기 코드처럼 작성
타입스크립트 통합타입 추론이 어려움제네릭을 통해 타입 안정성 확보가장 타입 안정적
테스트 용이성테스트 작성이 복잡할 수 있음테스트 작성이 비교적 쉬움가장 테스트 작성이 쉬움
디버깅콜백 중첩으로 인해 어려움스택 트레이스가 깔끔함동기 코드와 유사해 가장 쉬움
메모리 사용콜백 중첩 시 메모리 사용량 증가체이닝으로 인한 약간의 오버헤드일반적으로 가장 효율적
취소 가능성직접 구현 필요취소 불가능 (별도 구현 필요)취소 불가능 (별도 구현 필요)

구현 예시

  1. 콜백 함수 (Callbacks)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 콜백 함수를 사용한 비동기 처리 예시
function fetchData(callback) {
    // 데이터를 가져오는 비동기 작업 시뮬레이션
    setTimeout(() => {
        const data = { id: 1, name: "John" };
        callback(null, data);  // 성공시 첫 번째 인자는 null
    }, 1000);
}

fetchData((error, data) => {
    if (error) {
        console.error('에러 발생:', error);
        return;
    }
    console.log('데이터:', data);
});

하지만 콜백 방식은 여러 비동기 작업을 연달아 처리해야 할 때 “콜백 지옥"이라는 문제가 발생합니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fetchData((error, data1) => {
    if (error) return handleError(error);
    fetchMoreData(data1, (error, data2) => {
        if (error) return handleError(error);
        processData(data2, (error, data3) => {
            if (error) return handleError(error);
            // 이런 식으로 계속 중첩됨…
        });
    });
});
  1. Promise
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Promise를 사용한 비동기 처리 예시
function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const data = { id: 1, name: "John" };
            resolve(data);  // 성공시 resolve 호출
            // 실패시: reject(new Error('에러 메시지'));
        }, 1000);
    });
}

fetchData()
    .then(data => {
        console.log('데이터:', data);
        return processData(data);  // 다른 Promise 반환
    })
    .then(result => {
        console.log('처리된 결과:', result);
    })
    .catch(error => {
        console.error('에러 발생:', error);
    });

Promise는 여러 비동기 작업을 병렬로 처리하는 것도 가능하게 해줍니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 여러 Promise를 동시에 처리
Promise.all([
    fetchData1(),
    fetchData2(),
    fetchData3()
])
.then(([result1, result2, result3]) => {
    console.log('모든 데이터:', result1, result2, result3);
})
.catch(error => {
    console.error('하나라도 실패하면 호출됨:', error);
});
  1. Async/Await
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Async/Await를 사용한 비동기 처리 예시
async function fetchAndProcessData() {
    try {
        // await는 Promise가 완료될 때까지 기다립니다
        const data = await fetchData();
        console.log('데이터:', data);
        
        const processedData = await processData(data);
        console.log('처리된 결과:', processedData);
        
        return processedData;
    } catch (error) {
        console.error('에러 발생:', error);
        throw error;
    }
}

// async 함수는 항상 Promise를 반환합니다
fetchAndProcessData()
    .then(result => {
        console.log('최종 결과:', result);
    })
    .catch(error => {
        console.error('최종 에러 처리:', error);
    });

Async/Await도 여러 비동기 작업을 병렬로 처리할 수 있습니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
async function fetchAllData() {
    try {
        // Promise.all과 함께 사용하여 병렬 처리
        const [result1, result2, result3] = await Promise.all([
            fetchData1(),
            fetchData2(),
            fetchData3()
        ]);
        
        console.log('모든 결과:', result1, result2, result3);
    } catch (error) {
        console.error('에러 발생:', error);
    }
}

참고 및 출처