
1) Node.js
Node.js는 JavaScript를 어느 곳에서든 실행할 수 있게 해주는 JavaScript 실행환경이다.
( JavaScript's Runtime이라고 한다.)
Chrome V8 엔진으로 빌드되어 있다.
이벤트 기반, Non-Blocking I/O 모델을 사용해 가볍고 효율적이다.
1. 런타임(Runtime)
- 컴퓨터 프로그램이 실행되고 있는 동작을 의미함.
2. 이벤트 기반(Event-driven)
- 특정 이벤트 발생할 때 저장해둔 작업을 수행하는 방식.
3. Non-Blocking I/O
- I/O 작업이 진행되는 동안 프로세스의 작업을 중단시키지 않는 것.
*I/O : 입출력 (Input / Output)
2) 이벤트 기반(Event-driven)
특정 이벤트 발생할 때 저장해 둔 작업을 수행하는 방식. ex) 클릭, 네트워크 요청 등.
- 이벤트 루프(Event Loop)
- 여러 이벤트 동시 발생 시 어떤 순서로 콜백 함수를 호출할지 판단하는 역할.
- 노트가 종료될 때까지 이벤트 처리 작업을 반복하므로 루프(Loop)라고 부름. - 태스크 큐(Task Queue)
- 이벤트 발생 후 호출될 콜백 함수들이 기다리는 공간.
- 콜백들이 이벤트 루프가 정한 순서대로 있으므로 콜백 큐(Callback Queue)라고 함. - 백그라운드(Background)
- 타이머나 I/O작업 콜백 또는 이벤트 리스너들이 대기하는 공간.

- 이벤트 루프 동작 방식
1. 매크로 태스크 큐에서 가장 오래된 태스크를 꺼내서 실행.
2. 마이크로 태스크 큐에 있는 모든 태스크 실행.
3. 랜더링 작업 실행.
4. 매크로 태스크 큐에 다음 매크로 태스크가 올 때까지 대기.
5. 1. 번으로 돌아감.
*마이크로 태스크 큐 & 매크로 태스크 큐
- 태스크 큐는 마이크로 태스크 큐(Event Queue)와 매크로 태스크 큐(Job Queue)로 나뉨.
- API에 따라 마이크로 태스크 큐를 사용하거나 매크로 태스크 큐를 사용.
3) Non-Blocking I/O 모델
- I/O 작업이 진행되는 동안 프로세스의 작업을 중단시키지 않는 것.
- 때문에 I/O 작업이 많은 환경에서 효율이 좋음. ex) 넷플릭스

- 비동기 입출력
- 다른 주체의 작업 처리 여부와 관계없이 작업 수행하는 방식.
위 그래프에서 처럼 동일한 작업을 수행하더라도 Non-Blocking I/O 방식이 더 빠르게 처리됨.
단, Node.js에 가지는 싱글 스레드라는 한계로 인해 모두가 시간 이득을 볼 수 있는 것은 아님.
4) 콜백(Callback) 함수
- 다른 함수의 인자로써 넘겨진 후 특정 이벤트에 의해 호출되는 함수.
- 무엇인가 일을 다른객체에게 시키고, 그 객체가 나를 부를 때까지 할 일을 하고 있는 것.
- 따라서 Non-Block이며 비동기 방식의 함수로 사용됨.
4.1) 예시
let name;
setTimeout(() => {
name = 'Apple';
}, 3000);
console.log(name);
실행결과 : undefined
위 코드에서 setTimeout은 정해진 시간만큼 멈췄다가 코드를 실행하는 함수이다.
때문에 일반적으로는 3000ms로 정해져 있기 때문에 3초 뒤에 Apple이 출력될 것으로 생각할 것이다.
하지만 결과는 undefined가 나온다. 이것이 콜백이 필요한 이유이다.
위 코드에서 setTimeout 역시 비동기로 처리되는 함수이다. 때문에 3초간 기다리는 명령만 실행한 후 console.log(name)으로 넘어가는 것이다. 그것을 확인하기 위해서는 다음과 같이 코드를 수정할 수 있다.
let name;
setTimeout(() => {
name = 'Apple';
console.log(name);
}, 3000);
console.log(name);
실행결과: undefined Apple 순서로 출력.
위 코드로 수정 후 결과를 보면, name에 값이 할당되지 않은 상태로 마지막 줄 console.log(name)이 실행되고 setTimeout으로 지정한 3초의 시간이 경과된 후 콜백 함수가 실행되어 Apple를 출력한다.
마지막 예제를 보자.
const fd = require('fs');
function readFileContents(filePath, callback) {
fs.readFile(filePath, 'utf8', (err, data) => {
if(err) {
callback(err, null);
return;
}
callback(null, data);
});
}
readFileContents('example.txt', (err, data) => {
if(err) {
console.error('Error file:', err);
return;
}
console.log('File:', data);
});
위 코드는 특정 파일을 읽기 위한 코드이다. 파일을 읽는 동작은 시간이 오래 걸릴 수 있기 때문에, 파일을 읽는 동안 다른 일을 할 수 있도록 비동기적으로 파일을 읽을 필요가 있다.
코드를 순서대로 설명해 보겠다.
const fd = require('fs');
코드의 첫 줄은 Node.js의 파일 시스템 모듈을 불러오는 코드이다.
해당 모듈의 함수들은 아래 포스트를 참고하자.
https://homeless-programmer.tistory.com/3
[Node.js]: Node.js 입문하기 _ 모듈/함수
1) process 객체의 속성/이벤트 process 객체는 프로세스 정보를 제공/제어할 수 있게 하는 객체. 1.1) process 객체의 속성 속성 설명 env 컴퓨터의 환경정보를 표시 version Node.js 버전 표시 versions Node.js와
homeless-programmer.tistory.com
function readFileContents(filePath, callback) {
fs.readFile(filePath, 'utf8', (err, data) => {
if(err) {
callback(err, null);
return;
}
callback(null, data);
});
}
위 코드에서 readFileContents 함수는 파일을 비동기적으로 읽어오는 역할을 하며, 파일의 경로와 콜백 함수를 인자로 받는다. 그리고 fs.readFile 함수를 호출하여 파일을 읽어온다.
fs.readFile(filePath, 'utf8', (err, data) => { ... } 부분은 readFile 함수의 콜백함수이다. 해당 함수는 파일을 읽은 후 실행된다.
첫 번째 인자로 에러 객체(err), 두 번째 인자로 파일 내용(data)을 받는다.
파일을 읽는 과정에서 오류가 발생했을 시, 오류를 콜백 함수에 전달한다. 해당 내용은 if문에 작성되어 있다.
마지막으로 callback(null, data) 부분은, 파일을 정상적으로 읽어왔을 때 읽은 데이터를 콜백 함수에 전달하는 코드이다.
readFileContents('example.txt', (err, data) => {
if(err) {
console.error('Error file:', err);
return;
}
console.log('File:', data);
});
위 코드들은 readFileContents 함수를 호출하여 파일을 읽고, 콘솔에 파일 내용을 출력하는 코드이다. 해당 코드의 구조는 두 번째 부분과 동일하기 때문에 별다른 설명은 필요 없을 것 같다.
다만 이 함수는 파일을 읽은 후에 실행된다는 것만 알아두면 된다.
이런 식으로 콜백 함수를 사용하여 비동기적인 작업을 처리할 수 있다. 이러한 패턴은 Node.js에서 흔하게 사용되므로 익숙해질 필요가 있다.
4.2) 콜백 지옥(Callback Hell)
콜백 지옥은 콜백 함수를 중첩하여 사용하면서 코드가 길어지고 가독성이 낮아지는 현상을 의미함.
아래의 간단한 코드를 보면 쉽게 알 수 있다.
const fs = require('fs');
// 파일을 읽는 함수
function readFile(filePath, callback) {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
callback(err);
return;
}
callback(null, data);
});
}
// 파일을 쓰는 함수
function writeFile(filePath, data, callback) {
fs.writeFile(filePath, data, (err) => {
if (err) {
callback(err);
return;
}
callback(null);
});
}
// 파일을 읽고, 그 내용을 가지고 다시 파일을 쓰는 과정
readFile('input.txt', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
const modifiedData = data.toUpperCase();
writeFile('output.txt', modifiedData, (err) => {
if (err) {
console.error('Error writing file:', err);
return;
}
console.log('File written successfully!');
});
});
위 코드에서 readFile 함수를 사용하여 파일을 읽은 후 내용을 writeFile 함수를 사용해 새 파일에 쓴다.
하지만 writeFile 함수는 readFile 함수의 콜백 함수 안에 중첩 되어 사용되고 있다. 이러한 중첩되는 콜백 함수들이 여러 개 존재하게 되면 코드가 길어지면서 가독성이 떨어지게 되는 것이다.
이러한 콜백 지옥을 해결하기 위해 Promise나 async/await과 같은 방법을 사용한다.
'Node.js' 카테고리의 다른 글
| [Node.js]: Node.js 입문하기 _ express 모듈 (0) | 2024.04.03 |
|---|---|
| [Node.js]: Node.js 입문하기 _ 모듈/함수 (0) | 2024.04.01 |