[2026] JavaScript 비동기 프로그래밍 | Promise, async/await 완벽 정리

[2026] JavaScript 비동기 프로그래밍 | Promise, async/await 완벽 정리

이 글의 핵심

JavaScript 비동기 프로그래밍: Promise, async/await 콜백 (Callback)·Promise.

들어가며

비동기 프로그래밍이란?

비동기(Asynchronous)는 네트워크·타이머처럼 끝나는 시점이 불확실한 작업을 기다리느라 다음 줄을 막지 않고, 완료 시 콜백·프로미스·async/await로 결과를 받는 방식입니다. 동기 vs 비동기:

// 동기 (Synchronous): 순차적 실행
console.log("1");
console.log("2");
console.log("3");
// 출력: 1 2 3
// 비동기 (Asynchronous): 기다리지 않음
console.log("1");
setTimeout(() => console.log("2"), 1000);
console.log("3");
// 출력: 1 3 2 (1초 후)

왜 비동기가 필요한가?

  • 네트워크 요청: API 호출 (수백 ms ~ 수 초)
  • 파일 읽기/쓰기: 디스크 I/O
  • 타이머: setTimeout, setInterval
  • 사용자 입력: 클릭, 키보드 이벤트 Node.js는 런타임마다 다르지만, 전형적으로 단일 스레드 이벤트 루프 위에서 비슷한 패턴의 비동기 I/O를 씁니다. 반면 C++의 std::asynclaunch 정책에 따라 OS 스레드에 가까운 실행을 염두에 두므로, “비동기”의 이미지가 JS와는 다를 수 있습니다. Kotlin 코루틴이나 Rust의 async/await는 그 사이에서 런타임이 협력적 스케줄링을 맡는 경우가 많습니다.

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

1. 콜백 (Callback)

기본 콜백

function fetchData(callback) {
    setTimeout(() => {
        const data = { id: 1, name: "홍길동" };
        callback(data);
    }, 1000);
}
console.log("시작");
fetchData(data => {
    console.log("데이터:", data);
});
console.log("끝");
// 출력:
// 시작
// 끝
// 데이터: { id: 1, name: '홍길동' } (1초 후)

콜백 지옥 (Callback Hell)

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 콜백 지옥: 가독성 나쁨
getUser(userId, (user) => {
    getOrders(user.id, (orders) => {
        getOrderDetails(orders[0].id, (details) => {
            console.log(details);
        });
    });
});
// 피라미드 구조 (Pyramid of Doom)

2. Promise

Promise란?

Promise는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체입니다. 3가지 상태:

  • Pending: 대기 (초기 상태)
  • Fulfilled: 이행 (성공)
  • Rejected: 거부 (실패)

Promise 생성

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Promise 생성
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = true;
        
        if (success) {
            resolve("성공!");  // 이행
        } else {
            reject("실패!");   // 거부
        }
    }, 1000);
});
// Promise 사용
promise
    .then(result => {
        console.log(result);  // 성공!
    })
    .catch(error => {
        console.error(error);
    })
    .finally(() => {
        console.log("완료");
    });

Promise 체이닝

Promise 체이닝으로 여러 비동기 작업을 순차적으로 실행할 수 있습니다: 다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 비동기 함수들 정의
function fetchUser(userId) {
    // Promise 반환
    return new Promise((resolve) => {
        // 1초 후 사용자 데이터 반환 (API 호출 시뮬레이션)
        setTimeout(() => {
            resolve({ id: userId, name: "홍길동" });
        }, 1000);
    });
}
function fetchOrders(userId) {
    return new Promise((resolve) => {
        // 1초 후 주문 데이터 반환
        setTimeout(() => {
            resolve([{ id: 1, product: "노트북" }]);
        }, 1000);
    });
}
function fetchOrderDetails(orderId) {
    return new Promise((resolve) => {
        // 1초 후 주문 상세 데이터 반환
        setTimeout(() => {
            resolve({ id: orderId, price: 1200000 });
        }, 1000);
    });
}
// Promise 체이닝으로 순차 실행
fetchUser(1)
    // 1단계: 사용자 조회
    .then(user => {
        // user: { id: 1, name: "홍길동" }
        console.log("사용자:", user);
        
        // 다음 Promise 반환 (중요!)
        // return을 해야 다음 .then()으로 전달됨
        return fetchOrders(user.id);
    })
    // 2단계: 주문 조회
    .then(orders => {
        // orders: [{ id: 1, product: "노트북" }]
        console.log("주문:", orders);
        
        // 다음 Promise 반환
        return fetchOrderDetails(orders[0].id);
    })
    // 3단계: 주문 상세 조회
    .then(details => {
        // details: { id: 1, price: 1200000 }
        console.log("상세:", details);
        // 마지막 단계는 return 불필요
    })
    // 에러 처리: 어느 단계에서든 에러 발생 시 catch로 이동
    .catch(error => {
        console.error("에러:", error);
    })
    // finally: 성공/실패 관계없이 항상 실행
    .finally(() => {
        console.log("모든 작업 완료");
    });
// 출력 (3초 후):
// 사용자: { id: 1, name: '홍길동' }      (1초)
// 주문: [{ id: 1, product: '노트북' }]   (2초)
// 상세: { id: 1, price: 1200000 }        (3초)
// 모든 작업 완료

체이닝의 핵심 규칙: 다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ✅ 올바른 체이닝: Promise 반환
promise
    .then(result => {
        return anotherPromise();  // Promise 반환
    })
    .then(result2 => {
        // anotherPromise의 결과 받음
    });
// ❌ 잘못된 체이닝: return 없음
promise
    .then(result => {
        anotherPromise();  // return 없음!
    })
    .then(result2 => {
        // result2는 undefined
    });

콜백 지옥 vs Promise 체이닝: 다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 콜백 지옥 (Callback Hell)
getUser(userId, (user) => {
    getOrders(user.id, (orders) => {
        getOrderDetails(orders[0].id, (details) => {
            console.log(details);
        });
    });
});
// 들여쓰기가 깊어지고 가독성 나쁨
// ✅ Promise 체이닝 (깔끔)
getUser(userId)
    .then(user => getOrders(user.id))
    .then(orders => getOrderDetails(orders[0].id))
    .then(details => console.log(details))
    .catch(error => console.error(error));
// 평평한 구조, 가독성 좋음

Promise 정적 메서드

Promise는 여러 비동기 작업을 조합할 수 있는 정적 메서드를 제공합니다: 다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 1. Promise.resolve(): 즉시 이행된 Promise 생성
Promise.resolve(42).then(x => console.log(x));  // 42
// 이미 값이 있을 때 Promise로 감싸기
// 동기 값을 비동기 체인에 포함시킬 때 유용
// 2. Promise.reject(): 즉시 거부된 Promise 생성
Promise.reject("에러").catch(e => console.error(e));  // 에러
// 테스트나 에러 시뮬레이션에 사용
// 3. Promise.all(): 모든 Promise가 완료될 때까지 대기
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
    .then(results => console.log(results));  // [1, 2, 3]
// 배열의 모든 Promise가 성공해야 then 실행
// 결과는 입력 순서와 동일한 배열로 반환
// ❌ 하나라도 실패하면 전체 실패
Promise.all([
    Promise.resolve(1),
    Promise.reject("에러"),  // 이 에러로 인해
    Promise.resolve(3)
]).catch(error => console.error(error));  // 에러
// 첫 번째 에러만 catch로 전달됨
// 나머지 Promise는 무시됨
// 4. Promise.allSettled(): 모두 완료 대기 (실패 무시)
Promise.allSettled([
    Promise.resolve(1),
    Promise.reject("에러"),
    Promise.resolve(3)
]).then(results => console.log(results));
// [
//   { status: 'fulfilled', value: 1 },      // 성공
//   { status: 'rejected', reason: '에러' },  // 실패
//   { status: 'fulfilled', value: 3 }       // 성공
// ]
// 각 Promise의 성공/실패 여부와 결과를 모두 반환
// 실패해도 전체가 실패하지 않음 (all과의 차이)
// 5. Promise.race(): 가장 먼저 완료되는 것만 반환
Promise.race([
    new Promise(resolve => setTimeout(() => resolve(1), 1000)),
    new Promise(resolve => setTimeout(() => resolve(2), 500)),   // 가장 빠름
    new Promise(resolve => setTimeout(() => resolve(3), 1500))
]).then(result => console.log(result));  // 2 (500ms 후)
// 타임아웃 구현에 유용:
// Promise.race([fetchData(), timeout(5000)])
// 6. Promise.any(): 가장 먼저 성공하는 것 반환 (ES2021+)
Promise.any([
    Promise.reject("에러1"),  // 실패 무시
    new Promise(resolve => setTimeout(() => resolve(2), 500)),   // 첫 성공
    new Promise(resolve => setTimeout(() => resolve(3), 1000))
]).then(result => console.log(result));  // 2
// race와 달리 실패는 무시하고 첫 성공만 반환
// 모두 실패하면 AggregateError 발생

실전 활용 예시: 다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 병렬 API 호출 (Promise.all)
async function loadDashboard() {
    try {
        // 3개 API를 동시에 호출 (병렬)
        const [user, orders, notifications] = await Promise.all([
            fetchUser(),
            fetchOrders(),
            fetchNotifications()
        ]);
        // 모두 완료되면 한 번에 결과 받음
        
        renderDashboard(user, orders, notifications);
    } catch (error) {
        console.error("대시보드 로딩 실패:", error);
    }
}
// 타임아웃 구현 (Promise.race)
function timeout(ms) {
    return new Promise((_, reject) => 
        setTimeout(() => reject(new Error("시간 초과")), ms)
    );
}
async function fetchWithTimeout(url, ms = 5000) {
    return Promise.race([
        fetch(url),
        timeout(ms)
    ]);
}

3. async/await

async/await 기본

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// async 함수는 항상 Promise 반환
async function fetchData() {
    return "데이터";
}
fetchData().then(data => console.log(data));  // 데이터
// await: Promise가 완료될 때까지 대기
async function getData() {
    const data = await fetchData();
    console.log(data);  // 데이터
}
getData();

async/await 실전

다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
async function fetchUser(userId) {
    await delay(1000);
    return { id: userId, name: "홍길동" };
}
async function fetchOrders(userId) {
    await delay(1000);
    return [{ id: 1, product: "노트북" }];
}
async function fetchOrderDetails(orderId) {
    await delay(1000);
    return { id: orderId, price: 1200000 };
}
// async/await로 순차 실행
async function main() {
    try {
        console.log("시작");
        
        const user = await fetchUser(1);
        console.log("사용자:", user);
        
        const orders = await fetchOrders(user.id);
        console.log("주문:", orders);
        
        const details = await fetchOrderDetails(orders[0].id);
        console.log("상세:", details);
        
        console.log("완료");
    } catch (error) {
        console.error("에러:", error);
    }
}
main();
// 출력 (3초 후):
// 시작
// 사용자: { id: 1, name: '홍길동' }
// 주문: [{ id: 1, product: '노트북' }]
// 상세: { id: 1, price: 1200000 }
// 완료

병렬 실행

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 순차 실행 (느림)
async function sequential() {
    const user1 = await fetchUser(1);  // 1초
    const user2 = await fetchUser(2);  // 1초
    const user3 = await fetchUser(3);  // 1초
    return [user1, user2, user3];  // 총 3초
}
// 병렬 실행 (빠름)
async function parallel() {
    const [user1, user2, user3] = await Promise.all([
        fetchUser(1),
        fetchUser(2),
        fetchUser(3)
    ]);
    return [user1, user2, user3];  // 총 1초
}
// 또는
async function parallel() {
    const p1 = fetchUser(1);
    const p2 = fetchUser(2);
    const p3 = fetchUser(3);
    
    const user1 = await p1;
    const user2 = await p2;
    const user3 = await p3;
    
    return [user1, user2, user3];
}

4. 에러 처리

try-catch

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

async function fetchData() {
    try {
        const response = await fetch("https://api.example.com/data");
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const data = await response.json();
        return data;
    } catch (error) {
        console.error("에러 발생:", error.message);
        return null;
    } finally {
        console.log("요청 완료");
    }
}

Promise 에러 처리

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// then/catch
fetch("https://api.example.com/data")
    .then(response => {
        if (!response.ok) {
            throw new Error("HTTP error");
        }
        return response.json();
    })
    .then(data => console.log(data))
    .catch(error => console.error(error))
    .finally(() => console.log("완료"));
// catch는 체인 어디서든 발생한 에러 처리
Promise.resolve(1)
    .then(x => {
        throw new Error("에러!");
        return x + 1;
    })
    .then(x => x * 2)
    .catch(error => console.error(error.message))  // 에러!
    .then(() => console.log("계속 실행"));  // 계속 실행

5. 실전 예제

예제 1: API 호출

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

async function fetchGitHubUser(username) {
    try {
        const response = await fetch(`https://api.github.com/users/${username}`);
        
        if (!response.ok) {
            throw new Error(`User not found: ${response.status}`);
        }
        
        const user = await response.json();
        return {
            name: user.name,
            bio: user.bio,
            repos: user.public_repos
        };
    } catch (error) {
        console.error("에러:", error.message);
        return null;
    }
}
// 사용
fetchGitHubUser("torvalds").then(user => {
    if (user) {
        console.log(user);
    }
});

예제 2: 재시도 로직

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

async function fetchWithRetry(url, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
        try {
            const response = await fetch(url);
            if (response.ok) {
                return await response.json();
            }
        } catch (error) {
            console.log(`시도 ${i + 1} 실패`);
            
            if (i === maxRetries - 1) {
                throw new Error("최대 재시도 횟수 초과");
            }
            
            // 지수 백오프
            await new Promise(resolve => 
                setTimeout(resolve, 1000 * Math.pow(2, i))
            );
        }
    }
}
// 사용
fetchWithRetry("https://api.example.com/data")
    .then(data => console.log(data))
    .catch(error => console.error(error));

예제 3: 타임아웃

다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

function timeout(ms) {
    return new Promise((_, reject) => {
        setTimeout(() => reject(new Error("Timeout")), ms);
    });
}
async function fetchWithTimeout(url, ms = 5000) {
    try {
        const response = await Promise.race([
            fetch(url),
            timeout(ms)
        ]);
        return await response.json();
    } catch (error) {
        if (error.message === "Timeout") {
            console.error("요청 시간 초과");
        }
        throw error;
    }
}
// 사용
fetchWithTimeout("https://api.example.com/data", 3000)
    .then(data => console.log(data))
    .catch(error => console.error(error));

예제 4: 병렬 처리

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

async function fetchMultipleUsers(userIds) {
    const promises = userIds.map(id => fetchUser(id));
    
    try {
        const users = await Promise.all(promises);
        return users;
    } catch (error) {
        console.error("사용자 조회 실패:", error);
        return [];
    }
}
// 사용
fetchMultipleUsers([1, 2, 3]).then(users => {
    console.log(users);
});
// allSettled로 부분 실패 허용
async function fetchMultipleUsersSettled(userIds) {
    const promises = userIds.map(id => fetchUser(id));
    const results = await Promise.allSettled(promises);
    
    return results
        .filter(result => result.status === 'fulfilled')
        .map(result => result.value);
}

예제 5: 순차 처리

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 순차 처리: 이전 결과가 다음에 필요
async function processSequentially(items) {
    let result = 0;
    
    for (let item of items) {
        result = await processItem(item, result);
    }
    
    return result;
}
// reduce로 순차 처리
async function processSequentially(items) {
    return items.reduce(async (accPromise, item) => {
        const acc = await accPromise;
        return await processItem(item, acc);
    }, Promise.resolve(0));
}

6. 이벤트 루프 (Event Loop)

이벤트 루프란?

JavaScript는 싱글 스레드지만, 이벤트 루프로 비동기를 처리합니다.

console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// 출력: 1 4 3 2

실행 순서:

  1. 동기 코드: 1, 4
  2. Microtask Queue (Promise): 3
  3. Macrotask Queue (setTimeout): 2

Microtask vs Macrotask

// Macrotask: setTimeout, setInterval
setTimeout(() => console.log("Macrotask"), 0);
// Microtask: Promise, queueMicrotask
Promise.resolve().then(() => console.log("Microtask"));
console.log("Sync");
// 출력:
// Sync
// Microtask (먼저!)
// Macrotask

7. 실전 패턴

패턴 1: 로딩 상태 관리

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class DataFetcher {
    constructor() {
        this.loading = false;
        this.data = null;
        this.error = null;
    }
    
    async fetch(url) {
        this.loading = true;
        this.error = null;
        
        try {
            const response = await fetch(url);
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }
            this.data = await response.json();
        } catch (error) {
            this.error = error.message;
        } finally {
            this.loading = false;
        }
        
        return this.data;
    }
}
// 사용
const fetcher = new DataFetcher();
fetcher.fetch("https://api.example.com/data");
// React 스타일
async function loadData() {
    setLoading(true);
    try {
        const data = await fetchData();
        setData(data);
    } catch (error) {
        setError(error.message);
    } finally {
        setLoading(false);
    }
}

패턴 2: 캐싱

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class CachedFetcher {
    constructor() {
        this.cache = new Map();
    }
    
    async fetch(url) {
        if (this.cache.has(url)) {
            console.log("캐시에서 반환");
            return this.cache.get(url);
        }
        
        console.log("네트워크 요청");
        const response = await fetch(url);
        const data = await response.json();
        
        this.cache.set(url, data);
        return data;
    }
}
// 사용
const fetcher = new CachedFetcher();
await fetcher.fetch("https://api.example.com/data");  // 네트워크 요청
await fetcher.fetch("https://api.example.com/data");  // 캐시에서 반환

패턴 3: 큐 처리

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class TaskQueue {
    constructor(concurrency = 1) {
        this.concurrency = concurrency;
        this.running = 0;
        this.queue = [];
    }
    
    async add(task) {
        return new Promise((resolve, reject) => {
            this.queue.push({ task, resolve, reject });
            this.process();
        });
    }
    
    async process() {
        if (this.running >= this.concurrency || this.queue.length === 0) {
            return;
        }
        
        this.running++;
        const { task, resolve, reject } = this.queue.shift();
        
        try {
            const result = await task();
            resolve(result);
        } catch (error) {
            reject(error);
        } finally {
            this.running--;
            this.process();
        }
    }
}
// 사용
const queue = new TaskQueue(2);  // 동시 실행 2개
for (let i = 0; i < 10; i++) {
    queue.add(async () => {
        console.log(`작업 ${i} 시작`);
        await delay(1000);
        console.log(`작업 ${i} 완료`);
        return i;
    });
}

8. 자주 하는 실수와 해결법

실수 1: await 없이 Promise 사용

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 잘못된 방법
async function getData() {
    const data = fetchData();  // await 누락!
    console.log(data);  // Promise { <pending> }
}
// ✅ 올바른 방법
async function getData() {
    const data = await fetchData();
    console.log(data);  // 실제 데이터
}

실수 2: 순차 vs 병렬

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 불필요한 순차 실행 (느림)
async function slow() {
    const user1 = await fetchUser(1);  // 1초
    const user2 = await fetchUser(2);  // 1초
    return [user1, user2];  // 총 2초
}
// ✅ 병렬 실행 (빠름)
async function fast() {
    const [user1, user2] = await Promise.all([
        fetchUser(1),
        fetchUser(2)
    ]);
    return [user1, user2];  // 총 1초
}

실수 3: forEach에서 await

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ forEach는 async 무시
async function processItems(items) {
    items.forEach(async (item) => {
        await processItem(item);  // 기다리지 않음!
    });
    console.log("완료");  // 즉시 출력됨
}
// ✅ for...of 사용
async function processItems(items) {
    for (let item of items) {
        await processItem(item);  // 순차 대기
    }
    console.log("완료");  // 모두 완료 후 출력
}
// ✅ Promise.all로 병렬
async function processItems(items) {
    await Promise.all(items.map(item => processItem(item)));
    console.log("완료");
}

실수 4: try-catch 누락

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 에러 처리 없음
async function getData() {
    const data = await fetchData();  // 실패 시 프로그램 중단
    return data;
}
// ✅ 에러 처리
async function getData() {
    try {
        const data = await fetchData();
        return data;
    } catch (error) {
        console.error("에러:", error);
        return null;
    }
}

9. 연습 문제

문제 1: Promise 체이닝을 async/await로 변환

다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Promise 체이닝
function getDataPromise() {
    return fetchUser(1)
        .then(user => fetchOrders(user.id))
        .then(orders => fetchOrderDetails(orders[0].id))
        .catch(error => console.error(error));
}
// async/await 변환
async function getDataAsync() {
    try {
        const user = await fetchUser(1);
        const orders = await fetchOrders(user.id);
        const details = await fetchOrderDetails(orders[0].id);
        return details;
    } catch (error) {
        console.error(error);
    }
}

문제 2: 병렬 + 순차 조합

여러 사용자를 병렬로 가져온 후, 각 사용자의 주문을 순차적으로 처리하세요. 다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

async function processUsers(userIds) {
    // 1단계: 사용자 병렬 조회
    const users = await Promise.all(
        userIds.map(id => fetchUser(id))
    );
    
    // 2단계: 각 사용자의 주문 순차 처리
    const results = [];
    for (let user of users) {
        const orders = await fetchOrders(user.id);
        results.push({ user, orders });
    }
    
    return results;
}
// 테스트
processUsers([1, 2, 3]).then(results => {
    console.log(results);
});

문제 3: 프로미스 래퍼

콜백 기반 함수를 Promise로 변환하세요. 다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 콜백 기반
function readFileCallback(filename, callback) {
    setTimeout(() => {
        callback(null, `${filename}의 내용`);
    }, 1000);
}
// Promise 래퍼
function readFilePromise(filename) {
    return new Promise((resolve, reject) => {
        readFileCallback(filename, (error, data) => {
            if (error) {
                reject(error);
            } else {
                resolve(data);
            }
        });
    });
}
// 사용
readFilePromise("test.txt")
    .then(data => console.log(data))
    .catch(error => console.error(error));
// async/await
async function main() {
    try {
        const data = await readFilePromise("test.txt");
        console.log(data);
    } catch (error) {
        console.error(error);
    }
}

정리

핵심 요약

  1. 비동기 처리 방법:
    • 콜백: 가장 기본, 콜백 지옥 문제
    • Promise: 체이닝, 에러 처리 개선
    • async/await: 동기 코드처럼 작성 (가장 권장)
  2. Promise 메서드:
    • Promise.all(): 모두 완료 대기
    • Promise.allSettled(): 모두 완료 대기 (실패 무시)
    • Promise.race(): 가장 먼저 완료
    • Promise.any(): 가장 먼저 성공
  3. 에러 처리:
    • Promise: .catch()
    • async/await: try-catch
  4. 성능:
    • 순차: await 순서대로
    • 병렬: Promise.all()
  5. 이벤트 루프:
    • Microtask (Promise) > Macrotask (setTimeout)

베스트 프랙티스

  1. async/await 우선 사용
  2. ✅ 병렬 처리 가능하면 Promise.all()
  3. ✅ 항상 에러 처리 (try-catch)
  4. ✅ 타임아웃 설정
  5. ✅ 재시도 로직 구현

다음 단계


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3