[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
| 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
Resources
- Node.js async_hooks (advanced)
- MDN: Promise