[2026] JavaScript Promise & async/await Complete Guide | Async Patterns for Node & Browser

[2026] JavaScript Promise & async/await Complete Guide | Async Patterns for Node & Browser

이 글의 핵심

JavaScript Promise and async/await tutorial: callbacks vs Promises, Promise.all/race/allSettled, error handling, and the event loop—patterns for Node.js and frontend apps.

Introduction

What is asynchronous programming?

Asynchronous code does not block the rest of the program while waiting for a long-running operation. Sync vs async: 아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 실행 예제
console.log("1");
console.log("2");
console.log("3");
console.log("1");
setTimeout(() => console.log("2"), 1000);
console.log("3");

Why async matters:

  • Network: HTTP calls can take hundreds of ms or more
  • Disk: file I/O in Node.js
  • Timers: setTimeout, setInterval
  • User input: clicks, keyboard

1. Callbacks

Basic callback

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

function fetchData(callback) {
    setTimeout(() => {
        const data = { id: 1, name: "Alice" };
        callback(data);
    }, 1000);
}
console.log("start");
fetchData(data => {
    console.log("data:", data);
});
console.log("end");

Callback hell

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

getUser(userId, (user) => {
    getOrders(user.id, (orders) => {
        getOrderDetails(orders[0].id, (details) => {
            console.log(details);
        });
    });
});

2. Promises

What is a Promise?

A Promise represents the eventual outcome of an async operation. States:

  • Pending: initial
  • Fulfilled: success
  • Rejected: failure

Creating a Promise

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

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = true;
        if (success) resolve("ok");
        else reject("fail");
    }, 1000);
});
promise
    .then(result => console.log(result))
    .catch(err => console.error(err))
    .finally(() => console.log("done"));

Chaining

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

function fetchUser(userId) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ id: userId, name: "Alice" });
        }, 1000);
    });
}
function fetchOrders(userId) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve([{ id: 1, product: "laptop" }]);
        }, 1000);
    });
}
function fetchOrderDetails(orderId) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ id: orderId, price: 1200 });
        }, 1000);
    });
}
fetchUser(1)
    .then(user => {
        console.log("user:", user);
        return fetchOrders(user.id);
    })
    .then(orders => {
        console.log("orders:", orders);
        return fetchOrderDetails(orders[0].id);
    })
    .then(details => {
        console.log("details:", details);
    })
    .catch(err => console.error("error:", err))
    .finally(() => console.log("all steps finished"));

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

promise
    .then(result => {
        return anotherPromise();
    })
    .then(result2 => { });
promise
    .then(result => {
        anotherPromise();
    })
    .then(result2 => {
        // result2 may be undefined if you forgot return
    });

Static methods

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

Promise.resolve(42).then(console.log);
Promise.reject("err").catch(console.error);
Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)])
    .then(console.log);
Promise.all([
    Promise.resolve(1),
    Promise.reject("err"),
    Promise.resolve(3)
]).catch(console.error);
Promise.allSettled([
    Promise.resolve(1),
    Promise.reject("err"),
    Promise.resolve(3)
]).then(console.log);
Promise.race([
    new Promise(r => setTimeout(() => r(1), 1000)),
    new Promise(r => setTimeout(() => r(2), 500)),
]).then(console.log);
Promise.any([
    Promise.reject("e1"),
    new Promise(r => setTimeout(() => r(2), 500)),
]).then(console.log);

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

async function loadDashboard() {
    try {
        const [user, orders, notifications] = await Promise.all([
            fetchUser(),
            fetchOrders(),
            fetchNotifications()
        ]);
        renderDashboard(user, orders, notifications);
    } catch (error) {
        console.error("dashboard load failed:", error);
    }
}
function timeout(ms) {
    return new Promise((_, reject) =>
        setTimeout(() => reject(new Error("timeout")), ms)
    );
}
async function fetchWithTimeout(url, ms = 5000) {
    return Promise.race([fetch(url), timeout(ms)]);
}

3. async/await

Basics

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

async function fetchData() {
    return "data";
}
fetchData().then(console.log);
async function getData() {
    const data = await fetchData();
    console.log(data);
}

Sequential flow

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

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
async function fetchUser(userId) {
    await delay(1000);
    return { id: userId, name: "Alice" };
}
async function main() {
    try {
        const user = await fetchUser(1);
        const orders = await fetchOrders(user.id);
        const details = await fetchOrderDetails(orders[0].id);
        console.log(details);
    } catch (error) {
        console.error("error:", error);
    }
}

Parallelism

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

async function sequential() {
    const u1 = await fetchUser(1);
    const u2 = await fetchUser(2);
    return [u1, u2];
}
async function parallel() {
    const [u1, u2, u3] = await Promise.all([
        fetchUser(1),
        fetchUser(2),
        fetchUser(3)
    ]);
    return [u1, u2, u3];
}
async function parallel2() {
    const p1 = fetchUser(1);
    const p2 = fetchUser(2);
    return [await p1, await p2];
}

4. Error handling

try/catch with async/await

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

async function fetchData() {
    try {
        const response = await fetch("https://api.example.com/data");
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        return await response.json();
    } catch (error) {
        console.error(error.message);
        return null;
    } finally {
        console.log("request finished");
    }
}

Promise errors

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

fetch("https://api.example.com/data")
    .then(response => {
        if (!response.ok) throw new Error("HTTP error");
        return response.json();
    })
    .then(console.log)
    .catch(console.error)
    .finally(() => console.log("done"));

5. Practical examples

Fetch GitHub user

아래 코드는 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;
    }
}

Retry with backoff

아래 코드는 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(`attempt ${i + 1} failed`);
            if (i === maxRetries - 1) throw new Error("max retries exceeded");
            await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
        }
    }
}

Timeout wrapper

다음은 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("request timed out");
        throw error;
    }
}

Parallel fetch with partial failure

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

async function fetchMultipleUsers(userIds) {
    const promises = userIds.map(id => fetchUser(id));
    try {
        return await Promise.all(promises);
    } catch (error) {
        console.error("fetch failed:", error);
        return [];
    }
}
async function fetchMultipleUsersSettled(userIds) {
    const results = await Promise.allSettled(userIds.map(id => fetchUser(id)));
    return results
        .filter(r => r.status === "fulfilled")
        .map(r => r.value);
}

Sequential processing

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

async function processSequentially(items) {
    let result = 0;
    for (let item of items) {
        result = await processItem(item, result);
    }
    return result;
}
async function processSequentiallyReduce(items) {
    return items.reduce(async (accP, item) => {
        const acc = await accP;
        return processItem(item, acc);
    }, Promise.resolve(0));
}

6. Event loop

JavaScript runs one thread per realm, but the event loop schedules async work. 아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 실행 예제
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");

Rough order:

  1. Sync code: 1, 4
  2. Microtasks (Promises): 3
  3. Macrotasks (setTimeout): 2
setTimeout(() => console.log("macrotask"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("sync");

7. Patterns

Loading state

다음은 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;
    }
}

Simple cache

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

class CachedFetcher {
    constructor() {
        this.cache = new Map();
    }
    async fetch(url) {
        if (this.cache.has(url)) return this.cache.get(url);
        const response = await fetch(url);
        const data = await response.json();
        this.cache.set(url, data);
        return data;
    }
}

Concurrency queue

다음은 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 {
            resolve(await task());
        } catch (error) {
            reject(error);
        } finally {
            this.running--;
            this.process();
        }
    }
}

8. Common mistakes

Forgetting await

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

async function getData() {
    const data = fetchData();
    console.log(data);
}
async function getDataFixed() {
    const data = await fetchData();
    console.log(data);
}

Serial vs parallel

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

async function slow() {
    const u1 = await fetchUser(1);
    const u2 = await fetchUser(2);
    return [u1, u2];
}
async function fast() {
    return Promise.all([fetchUser(1), fetchUser(2)]);
}

forEach with await

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

async function bad(items) {
    items.forEach(async item => {
        await processItem(item);
    });
    console.log("done too early");
}
async function goodSequential(items) {
    for (const item of items) await processItem(item);
    console.log("done");
}
async function goodParallel(items) {
    await Promise.all(items.map(processItem));
    console.log("done");
}

Missing try/catch

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

async function getData() {
    try {
        return await fetchData();
    } catch (error) {
        console.error(error);
        return null;
    }
}

9. Exercises

Rewrite chain as async/await

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

async function getDataAsync() {
    try {
        const user = await fetchUser(1);
        const orders = await fetchOrders(user.id);
        return await fetchOrderDetails(orders[0].id);
    } catch (error) {
        console.error(error);
    }
}

Parallel users, sequential orders per user

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

async function processUsers(userIds) {
    const users = await Promise.all(userIds.map(id => fetchUser(id)));
    const results = [];
    for (const user of users) {
        const orders = await fetchOrders(user.id);
        results.push({ user, orders });
    }
    return results;
}

Promisify a callback API

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

function readFileCallback(filename, callback) {
    setTimeout(() => {
        callback(null, `contents of ${filename}`);
    }, 1000);
}
function readFilePromise(filename) {
    return new Promise((resolve, reject) => {
        readFileCallback(filename, (error, data) => {
            if (error) reject(error);
            else resolve(data);
        });
    });
}

Summary

Key points

  1. Styles: callbacks (basic), Promises (composition), async/await (readability).
  2. Static helpers: Promise.all, Promise.allSettled, Promise.race, Promise.any.
  3. Errors: .catch on chains; try/catch around await.
  4. Performance: sequential await vs Promise.all for independence.
  5. Event loop: microtasks before the next macrotask.

Best practices

  1. Prefer async/await for linear flow.
  2. Use Promise.all when tasks are independent.
  3. Always handle errors.
  4. Add timeouts for network calls when appropriate.
  5. Consider retries with backoff for flaky APIs.

Next steps


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