동기 메서드와 비동기 메서드
setTimeout 같은 타이머와 process.nextTrick 외에도, 노드는 대부분의 메서드를 비동기 방식으로 처리합니다. 하지만 몇몇 메서드는 동기 방식으로도 사용할 수 있습니다. 특히 fs 모듈이 그러한 메서드를 가지고 있습니다.
readme2.txt
저를 여러 번 읽어보세요.
async.js
const fs = require("fs");
console.log("시작");
fs.readFile("./readme2.txt", (err, data) => {
if (err) {
throw err;
}
console.log("1번", data.toString());
});
fs.readFile("./readme2.txt", (err, data) => {
if (err) {
throw err;
}
console.log("2번", data.toString());
});
fs.readFile("./readme2.txt", (err, data) => {
if (err) {
throw err;
}
console.log("3번", data.toString());
});
console.log("끝");
같은 파일을 세 번 읽었습니다.
콘솔
$ node async
시작
끝
2번 저를 여러 번 읽어보세요.
3번 저를 여러 번 읽어보세요.
1번 저를 여러 번 읽어보세요.
시작과 끝을 제외하고는 결과의 순서가 다를 수 있습니다.
비동기 메서드들은 백그라운드에 해당 파일을 읽으라고만 요청하고 다음 작업으로 넘어갑니다. 따라서 파일 읽기 요청만 세 번을 보내고 console.log('끝')을 찍습니다. 나중에 읽기가 완료되면 백그라운드가 다시 메인 스레드에 알립니다. 메인 스렌드는 그제서야 등록된 콜백 함수를 실행합니다.
이 방식은 상당히 좋습니다. 수백 개의 I/O 요청이 들어와도 메인 스레드는 백그라운드에 요청 처리를 위임합니다. 그 후로도 얼마든지 요청을 더 받을 수 있습니다. 나중에 백그라운드가 각각의 요청 처리가 완료되었다고 말하면 그때 콜백 함수를 처리하면 됩니다.
백그라운드에서는 요청 세 개를 거의 동시에 실행합니다.
순서대로 출력하고 싶다면 다음 메서드를 사용할 수도 있습니다.
sync.js
const fs = require("fs");
console.log("시작");
let data = fs.readFileSync("./readme2.txt");
console.log("1번", data.toString());
data = fs.readFileSync("./readme2.txt");
console.log("2번", data.toString());
data = fs.readFileSync("./readme2.txt");
console.log("3번", data.toString());
console.log("끝");
콘솔
$ node sync
시작
1번 저를 여러 번 읽어보세요.
2번 저를 여러 번 읽어보세요.
3번 저를 여러 번 읽어보세요.
끝
콘솔도 순서대로 찍힙니다. 코드는 훨씬 이해하기 쉽지만 치명적인 단점이 있습니다.
readFileSync 메서드를 사용하면 요청이 수백 개 이상 들어올 때 성능에 문제가 생깁니다. Sync 메서드를 사용할 때는 이전 작업이 완료되어야 다음 작업을 진행할 수 있습니다. 즉, 백그라운드가 작업하는 동안 메인 스레드는 아무것도 하지 못하고 대기하고 있어야 하는 것입니다. 메인 스레드가 일을 하지 않고 노는 시간이 생기므로 비효율적입니다. 백그라운드는 fs 작업을 동시에 처리할 수 있도 있는데, Sync 메서드를 사용하면 백그라운드조차 동시에 처리할 수 없게 됩니다. 비동기 fs 메서드를 사용하면 백그라운드가 동시에 작업할 수도 있고, 메인 스레드는 다음 작업을 처리할 수 있습니다.
동기 메소드들은 이름 뒤에 Sync가 븥어 있어 구분하기 쉽습니다. writeFileSync도 있습니다. 하지만 동기 메서드를 사용해야 하는 경우는 극하 드뭅니다. 프로그램을 처음 실행할 때 초기화 용도로만 사용하는 것을 권장합니다. 대부분의 경우에는 비동기 메소드가 훨씬 더 효율적입니다.
asyncOrder.js
const fs = require("fs");
console.log("시작");
fs.readFile("./readme2.txt", (err, data) => {
if (err) {
throw err;
}
console.log("1번", data.toString());
fs.readFile("./readme2.txt", (err, data) => {
if (err) {
throw err;
}
console.log("2번", data.toString());
fs.readFile("./readme2.txt", (err, data) => {
if (err) {
throw err;
}
console.log("3번", data.toString());
console.log("끝");
});
});
});
이제 readFile의 콜백에 다음 readFile을 넣으면 됩니다. 이른바 콜백 지옥이 펼쳐지지만 적어도 순서가 어긋나는 일을 없습니다.
콘솔
$ node asyncOrder
시작
1번 저를 여러 번 읽어보세요.
2번 저를 여러 번 읽어보세요.
3번 저를 여러 번 읽어보세요.
끝
콜백 지옥은 Promise나 async/await으로 어느 정도 해결할 수 있습니다.
asyncOrderPromise.js
const fs = require("fs").promises;
console.log("시작");
fs.readFile("./readme2.txt")
.then((data) => {
console.log("1번", data.toString());
return fs.readFile("./readme2.txt");
})
.then((data) => {
console.log("2번", data.toString());
return fs.readFile("./readme2.txt");
})
.then((data) => {
console.log("3번", data.toString());
console.log("끝");
})
.catch((err) => {
console.error(err);
});
실행 결과는 asyncOrder.js와 같습니다.
'프로그래밍 언어 > NODE JS' 카테고리의 다른 글
| 기타 fs 메서드 알아보기 (0) | 2025.05.01 |
|---|---|
| 버퍼와 스트림 이해하기 (0) | 2025.04.28 |
| 파일 시스템 접근하기 (0) | 2025.04.22 |
| 기타 모듈들 (0) | 2025.04.19 |
| worker_threads (0) | 2025.04.13 |