node app.js 파일을 실행을 하게 되면, node.js가 파일전체를 살펴보고 -> 코드를 분석한다.
이떄 변수와 함수들을 등록하며 이벤트가 종료 될때 까지 프로그램을 종료하지 않는다.
이유는 Node.js에서 Event Loop라는 개념이 존재 하기 때문인데, Event Loop란, 작업이 남아 있는 한 계속해서 작동하는 루프 프로세스로 이벤트 리스너가 있는 한 계속해서 작동이 됀다.
Event Loop는 코드를 실행하면 자동으로 시작이 되는 구조이며, Event Loop는 Event CallBack Handler를 다룬다.
특정 이벤트가 일어난 경우 eventLoop가 해당 코드를 실행하고 Event CallBack Handler가 모든 콜백을 파악하고 있기 때문에 가능한 일이다.
node.js도 단일 스레드 javascript를 사용하기 때문인데,
실행중인 컴퓨터에서 전체 노드 프로세스가 하나의 스레드를 사용하기 때문이다.
그러면 수많은 요청이 들어오는 경우, 스레드를 하나만 사용하게 되면 느려지는거 아닌가,? 라는 생각이 들었고 파일 업로드 같은 오래 걸리는 작업에는 EventHandler가 도움이 되지 않는거 아닌가..??
찾아보니 이벤트핸들러는 이런 작업을 다루지 않고, 단지 완성된 쓰기 파일에 정의한 콜백 코드 대해서만 다룬다. 즉, Event Loop는 빨리 끝낼 수 있는 코드를 포함한 콜백에 대해서만 다룬다.
그러면 파일 시스템과 같은 오래걸리는 작업들은 'Worcker Pool'로 보내 진다.
( 역시 Worcker Pool도 Node.js가 자동으로 시작하고 관리한다. )
Worcker Pool은 javascript 코드로 부터 완전히 분리되어 다른 여러 스레드에서 작동 될 수 있다.
Worcker가 작업을 마치면 파일연산에 대한 콜백이 시작되는데 이벤트 루프가 이벤트와 콜백을 담당하기 때문에
wocker의 작업결과가 결국 이벤트 루프에 들어가게 될 것이고(=trigger) node.js가 알맞은 콜백을 선택하게 된다.
결국 이벤트 루프란 Node.js에 의해 실행되어 Node.js를 실행하도록 하는 루프로 모든 콜백을 처리한다.
Event-Callback이 무수히 많은 경우 Node는 어떻게 처리하지??
각 콜백별 우선순위를 무작위로..??
역시 콜백을 처리하는데에는 "일정한 순서"가 존재한다.
새로운 반복이 시작될때 마다 실행해야 하는 timer callBack이 있는지 확인한다.(ex. setTimeOut, setInterval) timer callBack을 확인한 후 다른 callBack을 확인한다.(ex. 파일 읽기 및 쓰기)
만약 아직 처리되지 않은 콜백이 너무 많은 경우 루프 반복을 이어가는 대신, 남은 콜백을 다음 반복에서 실행하도록 미룬다.
열린 콜백을 모두 처리하고 나면, Poll 단계에 진입하고, Poll 단계에서는 Node.js가 새로운I/O 이벤트를 찾아 최대한 해당 이벤트의 콜백을 빨리 실행하도록 하고 가능하지 않다면 실행을 미루고 대기 콜백으로 등록한다. 또한 타이머가 다 되어 실행해야하는 콜백도 확인하게 되는데 만약 존재 하는 경우 Timers 단계로 넘어가 바로 실행하기도 한다. 즉, 반복을 이어가지 않고 다시 돌아갈 수도 있다.
이 다음, setImmediate callBack이 실행되고 setImmediate은 setTimeOut, setInterval 처럼 바로 실행이 되긴 하지만, 반드시 열린 콜백이 모두 실행된 다음에 실행되어 진다. 현재 주기가 끝나거나 적어도 한 반복에 열린 콜백을 처리한 후에 일어난다. 마지막으로 닫힌 이벤트 콜백까지 실행이 되면 프로그램을 종료한다.
프로그램을 종료하기 전에 반드시 등록한 이벤트 핸들러가 남지 않았는지 확인해야 한다.
Node.js는 내부적으로 열린 이벤트 리스너를 추적해서 references 나 ref로 숫자를 센다.
새로운 콜백이 등록되거나 이벤트 리스너가 등록될때마다 +1씩 증가하고 콜백이 완료되거나 필요 없어진 경우 -1씩 감소한다.
<Event callBack 처리순서>
아래 코드를 보면, 노드는 함수를 함수 안에 넣으면 안에 중첩된 함수가 나중에 실행이 되는데 이를 "비동기식(Non Blocking 방식)"이라고 한다.
함수 안에 넣은 함수가 항상 나중에 실행되는 것은 아니지만, 비동기식 코드를 만난 경우 내부적으로 이벤트 리스너가 하나 추가된다.
따라서 if문의 조건이 충족되는 경우 해당 if내의 코드가 바로 실행이 되는 것이 아니라, req.on('data'), req.on('end') 이벤트를 등록하고 해당 이벤트가 발생한 경우에 if문내의 작성되 코드가 실행이 되는 것이다.
(이벤트 루프가 노드 js의 모든 리스너들을 내부적으로 관리 한다.)
const http = require("http");
const fs = require("fs");
const { buffer } = require("stream/consumers");
const server = http.createServer((req, res) => {
const url = req.url;
const method = req.method;
if (url === "/") {
res.write("<html>");
res.write("<header><title>Enter Messages</title></header>");
res.write(
"<body><form action='/message' method='POST'><input type='text' name='message'><button type='submit'>Send</button></form></body>"
);
res.write("</html>");
return res.end();
}
if (url === "/message" && method === "POST") {
const body = [];
req.on("data", (chunk) => {
console.log("chunk", chunk);
body.push(chunk);
});
return req.on("end", () => { //return의 역할 : 'end' 리스너가 설정되면 즉시 호출 코드로 제어를 반환하여 동일한 함수 내의 후속라인의 실행을 방지한다.
const parsedBody = Buffer.concat(body).toString();
console.log("parsedBody", parsedBody);
const message = parsedBody.split("=")[1];
fs.writeFile("message.txt", message, (err) => {
res.statusCode = 302;
res.setHeader("Location", "/");
return res.end();
}); //writeFileSync: message.txt file이 생성되기 전까지 코드 실행을 막음
});
}
res.setHeader("Content-Type", "text/html");
res.write("<html>");
res.write("<header><title>My First Page</title></header>");
res.write("<body><h1>Hello from my Node.js Server</h1></body>");
res.write("</html>");
res.end();
});
server.listen(3000);
NodeJS => Non Blocking 방식으로 작동이 된다.
즉, 수많은 콜백과 이벤트를 등록해 두면 특정 작업이 끝난 후에 Node JS가 해당 코드를 작동 시킨다.
JavaScript 스레드는 항상 새 이벤트나 새로 들어오는 요청을 다룰 수 있으며 더 이상 작업이 없는 경우에는 프로그램을 종료한다.
단, createServer() Event는 새로운 요청이 들어와도 이벤트 리스너의 등록을 취소 하지 않는다. JavaScript 코드는 Non Blocking 방식이어야 해서, 콜백 등 이벤트 기반 방식을 이용하는 것이고,
코드가 바로 실행돼서 메인 스레드를 막지 않도록, 등록을 통해 이벤트를 등록하고, 해당 이벤트가 발생하면 해당 이벤트 후속코드가 실행하게 하도록 설계되어 있다.
비동기식 특성에 따라 코드가 바로 실행되지 않을 수도 있기에 이중 응답을 보내지 않도록 주의 해야 한다. 따라서 return을 이벤트 후속절차의 종료 시점이나 또 다른 이벤트가 실행되기 전에 작성하여 미리 오류를 방지하거나 Debug tool을 이용하여 해당 로직을 살펴봐 수정할 수 있다.
이러한 Node.js 특성에 따라 코드 작성위치에 따라 실행되지 않거나 다른 결과가 나올수 도 있다.