본문으로 건너뛰기
Previous
Next
Node.js Async Programming: Callbacks, Promises,

Node.js Async Programming: Callbacks, Promises,

Node.js Async Programming: Callbacks, Promises,

이 글의 핵심

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.

// 실행 예제
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](/en/blog/cpp-series-29-1-asio-intro/, Rust [concurrency](/en/blog/rust-series-07-concurrency/, and browser [async JavaScript](/en/blog/javascript-series-05-async/.

1. Callbacks

Error-first callbacks

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:

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

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

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:

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:

// 실행 예제
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:

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

Promise.any:

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.

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):

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

Parallel (faster):

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)

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

Typical ordering example:

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:

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.

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:

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

Transform:

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

// 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

  • [Express.js guide](/en/blog/nodejs-series-04-express/
  • [File system (fs)](/en/blog/nodejs-series-05-filesystem/
  • Database integration

Resources



자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Learn Node.js async I/O: callbacks, error-first style, Promises, async/await, the event loop, streams, and patterns for … 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • [Node.js Module System: CommonJS and ES Modules Explained](/en/blog/nodejs-series-02-modules/
  • [Express.js Complete Guide: Node.js Web Framework and REST](/en/blog/nodejs-series-04-express/
  • [JavaScript Async Programming](/en/blog/javascript-series-05-async/
  • [Boost.Asio Introduction: io_context, async_read, and](/en/blog/cpp-series-29-1-asio-intro/
  • [Rust Concurrency | Threads, Channels, Arc, and Mutex](/en/blog/rust-series-07-concurrency/

이 글에서 다루는 키워드 (관련 검색어)

Node.js, JavaScript, Async, Promise, await, Callback, Event Loop 등으로 검색하시면 이 글이 도움이 됩니다.