[2026] Node.js Async Programming: Callbacks, Promises, and Async/Await

[2026] Node.js Async Programming: Callbacks, Promises, and Async/Await

이 글의 핵심

Learn Node.js async I/O: callbacks, error-first style, Promises, async/await, the event loop, streams, and patterns for APIs and file pipelines—essential for Express and production Node apps.

Introduction

What is asynchronous programming?

Asynchronous code does not block the caller while waiting for I/O; other work can proceed. 아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

Why Node uses async:

  • Non-blocking I/O keeps the server responsive under load
  • High concurrency on a single thread for I/O-bound workloads
  • Less CPU wasted waiting on disks and networks Other ecosystems: compare with C++ Asio async I/O, Rust concurrency, and browser async JavaScript.

1. Callbacks

Error-first callbacks

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

const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
    if (err) {
        console.error(err.message);
        return;
    }
    console.log(data);
});

Callback hell

Chaining multiple async steps nests callbacks and duplicates error handling: 다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const fs = require('fs');
fs.readFile('file1.txt', 'utf8', (err1, data1) => {
    if (err1) {
        console.error(err1);
        return;
    }
    fs.readFile('file2.txt', 'utf8', (err2, data2) => {
        if (err2) {
            console.error(err2);
            return;
        }
        fs.readFile('file3.txt', 'utf8', (err3, data3) => {
            if (err3) {
                console.error(err3);
                return;
            }
            console.log(data1, data2, data3);
        });
    });
});

Issues: readability, repeated if (err), hard debugging. Fix: Promises or async/await.

2. Promises

Creating a Promise

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

function delay(ms) {
    return new Promise((resolve, reject) => {
        if (ms < 0) {
            reject(new Error('ms must be non-negative'));
            return;
        }
        setTimeout(() => resolve(`${ms}ms done`), ms);
    });
}
delay(1000)
    .then((result) => console.log(result))
    .catch((err) => console.error(err.message));

Chaining

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

const fs = require('fs').promises;
fs.readFile('file1.txt', 'utf8')
    .then((data1) => {
        console.log('file1:', data1);
        return fs.readFile('file2.txt', 'utf8');
    })
    .then((data2) => {
        console.log('file2:', data2);
        return fs.readFile('file3.txt', 'utf8');
    })
    .then((data3) => console.log('file3:', data3))
    .catch((err) => console.error('Error:', err.message))
    .finally(() => console.log('done'));

Static helpers

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

const fs = require('fs').promises;
Promise.all([
    fs.readFile('file1.txt', 'utf8'),
    fs.readFile('file2.txt', 'utf8'),
    fs.readFile('file3.txt', 'utf8')
])
    .then(([a, b, c]) => console.log(a, b, c))
    .catch((err) => console.error(err.message));

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

// 실행 예제
Promise.allSettled([
    fs.readFile('file1.txt', 'utf8'),
    fs.readFile('file2.txt', 'utf8'),
    fs.readFile('missing.txt', 'utf8')
]).then((results) => {
    results.forEach((r, i) => {
        if (r.status === 'fulfilled') console.log(i, r.value);
        else console.log(i, r.reason.message);
    });
});

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

// 실행 예제
Promise.race([
    delay(1000).then(() => '1s'),
    delay(2000).then(() => '2s'),
    delay(500).then(() => '0.5s')
]).then((result) => console.log('Fastest:', result));

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

Promise.any([
    Promise.reject('e1'),
    Promise.reject('e2'),
    Promise.resolve('ok')
]).then(console.log).catch(console.error);

3. Async/await

async functions always return a Promise. await pauses only the async function until the Promise settles; the event loop can still run other work. 다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const fs = require('fs').promises;
async function readFiles() {
    try {
        const data1 = await fs.readFile('file1.txt', 'utf8');
        const data2 = await fs.readFile('file2.txt', 'utf8');
        const data3 = await fs.readFile('file3.txt', 'utf8');
        console.log(data1, data2, data3);
        return 'all read';
    } catch (err) {
        console.error('Error:', err.message);
        throw err;
    }
}
readFiles()
    .then((msg) => console.log(msg))
    .catch((err) => console.error('Final error:', err.message));

Sequential vs parallel

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

async function sequential() {
    const start = Date.now();
    await delay(1000);
    await delay(1000);
    await delay(1000);
    console.log(Date.now() - start); // ~3000ms
}

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

async function parallel() {
    const start = Date.now();
    await Promise.all([delay(1000), delay(1000), delay(1000)]);
    console.log(Date.now() - start); // ~1000ms
}

4. Event loop (overview)

다음은 code를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

   ┌───────────────────────────┐
┌─>│           timers          │  setTimeout, setInterval
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │  I/O callbacks
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           poll            │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           check           │  setImmediate
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──│      close callbacks      │
   └───────────────────────────┘

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

console.log('1. sync');
setTimeout(() => console.log('4. setTimeout'), 0);
setImmediate(() => console.log('5. setImmediate'));
Promise.resolve().then(() => console.log('3. Promise microtask'));
console.log('2. sync');
process.nextTick(() => console.log('3b. nextTick'));

Rule of thumb: run sync code first, then nextTick, then other microtasks (Promises), then timers / I/O / setImmediate depending on context. Avoid starving the loop with excessive nextTick.

5. util.promisify

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

const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
async function main() {
    const data = await readFile('file.txt', 'utf8');
    console.log(data);
}
main();

6. Streams

Streams process data in chunks instead of loading entire files into memory. 다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const fs = require('fs');
const readStream = fs.createReadStream('large-file.txt', {
    encoding: 'utf8',
    highWaterMark: 64 * 1024
});
readStream.on('data', (chunk) => console.log('chunk', chunk.length));
readStream.on('end', () => console.log('done'));
readStream.on('error', (err) => console.error(err.message));
const writeStream = fs.createWriteStream('output.txt');
writeStream.write('line1\n');
writeStream.end('last\n');
writeStream.on('finish', () => console.log('written'));

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

// 변수 선언 및 초기화
const fs = require('fs');
const zlib = require('zlib');
fs.createReadStream('input.txt')
    .pipe(zlib.createGzip())
    .pipe(fs.createWriteStream('input.txt.gz'));

Transform: 아래 코드는 javascript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const { Transform } = require('stream');
class UpperCaseTransform extends Transform {
    _transform(chunk, encoding, callback) {
        this.push(chunk.toString().toUpperCase());
        callback();
    }
}
fs.createReadStream('input.txt')
    .pipe(new UpperCaseTransform())
    .pipe(fs.createWriteStream('output.txt'));

7. Practical examples

Full examples in this guide cover: file pipeline, fetch with retry, concurrency limit, timeout with Promise.race, download helper, batch processor, and simple crawler. Translate log messages to your team’s language in your projects.

8. Common pitfalls

Unhandled rejections

Always await inside try/catch or attach .catch(), and consider:

process.on('unhandledRejection', (reason) => {
    console.error('Unhandled rejection:', reason);
});

Missing await

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

// Wrong: data is a Promise
const data = fetchData();
// Right
const data = await fetchData();

forEach does not await

Use for...of with await, or Promise.all with map.

Summary

StyleReadabilityError handling
CallbacksPoor when nestedPer callback
PromisesBetter chaining.catch / end of chain
async/awaitBest for linear flowtry/catch

Next steps

Resources


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