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
| Style | Readability | Error handling |
|---|---|---|
| Callbacks | Poor when nested | Per callback |
| Promises | Better chaining | .catch / end of chain |
| async/await | Best for linear flow | try/catch |
Next steps
- [Express.js guide](/en/blog/nodejs-series-04-express/
- [File system (
fs)](/en/blog/nodejs-series-05-filesystem/ - Database integration
Resources
- Node.js async_hooks (advanced)
- MDN: Promise
Related posts
- JavaScript async: Promises and async/await
- [Getting started with Node.js](/en/blog/nodejs-series-01-intro/
자주 묻는 질문 (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 등으로 검색하시면 이 글이 도움이 됩니다.