본문 바로가기
Node.JS

Node.js란?

by 이곳느 2020. 8. 23.

Node.js는 무엇인가?

Java 언어가 모든 OS 운영체제에서 Virtual Machine 환경 안에서 Runtime 이 구동 되듯이 Node.JS 는 웹브라우저에 종속적인 자바스크립트에서 외부에서 실행할 수 있는 Runtime 환경을 Chrome V8 엔진을 제공하여 여러 OS 환경에서 실행할 수 있는 환경을 제공하게 됩니다. 이것을 Node.JS 라고 정의할 수 있습니다.

호출 스택과 이벤트 루프

function first() {
   second()
   console.log('첫 번째 실행')
}
function second() {
   third()
   console.log('두 번째 실행')
}
function third() {
   console.log('세 번째 실행')
}
first()

 

console 로 찍히는 순서는 어떻게 되는가 에 대한 문제입니다.. 답은 무엇일까요?

답은 세 번째, 두 번째, 첫 번째 실행으로 실행 됩니다. 이 부분의 실행 순서를 이야기 하기 위해선 호출 스택의 자료 구조로 first(), second(), third() 가 메모리에 들어가게 되어 있습니다. 스택의 특징으로는 Last In First Out(후입선출) 의 특징을 가지고 있습니다.

위의 코드는 first, second, third 함수가 정의 되어있고 first 함수 부터 호출을 합니다. 체이닝으로 실행이 연결되어 있고 first가 먼저 호출 되어있지만 second 함수는 first 함수에서 호출되고 second 함수는 third 함수에서 호출되게 됩니다. third 함수는 console.log를 실행하게 되고 가장 윗 부분에 상주해 있다가 스택에서 빠져 나오게 됩니다. 그 다음 second 안에 있는 console.log를 실행하게 되고 최종적으로는 first의 console.log를 가장 나중에 실행하게 됩니다. 결과는 아래와 같습니다.

 

그렇다면 아래 코드는 어떤 순서대로 실행이 될까요?

function run() {
   console.log(`3초 후 실행함`)
}
console.log(`시작`)
setTimeout(run, 3000)
console.log(`끝`)

정답은 아래와 같이 실행이 됩니다.

 


setTimeout은 시간을 지연시켜 함수를 실행하는 타이머 함수입니다. 시간은 정해져있지만 실제로 자바스크립트는 비동기 처리에 대한 함수가 많기 때문에 위의 예제를 통해 이벤트 루프를 설명하자면 이렇습니다.

실제로 동기적인 처리를 할 때는 자바스크립트는 컨텍스트 상 호출 스택 구조에서 가장 나중에 처리된 놈이 가장 먼저 처리 되는 현상을 볼 수가 있었습니다. 그 다음 console.log를 통해 찍어주는 단순한 키워드인 ‘시작' 과 ‘끝' 이후 setTimeout 이라는 타이머 함수를 통해 ‘3초 후에 실행함' 이라는 키워드가 가장 마지막에 동작하는 것을 보실 수 있었습니다. 이 때 비동기적인 결과에 대한 부분들은 태스크 큐라는 부분에 적재를 시키게 되고 큐는 자료구조상 First In First Out(선입선출) 특성을 가지고 있습니다.

이 때 3초 뒤에 스택으로 넘겨서 태스크가 실행이 되도록 하게 할 것이냐 라는 문제를 관리하는 놈이 바로 이벤트루프입니다. 이상 대략적인 스택, 태스크 큐 그리고 이벤트 루프의 설명이였습니다.

Node Module System

프론트에서 대부분 <script src=’{소스위치}’> 자바스크립트 임포트 모듈을 통해 변수나 오브젝트를 불러오는 방법이 있었다면 Node.JS에서는 아래와 같이 다른 자바스크립트 파일에서 참조할 수 있습니다.

// 참조해야 하는 constants.js 파일
module.exports = {
    sayHello : 'Hello!',
    name : 'CaptainChain'  
}

위의 선언된 변수와 오브젝트들은 아래의 코드처럼 참조가 가능합니다.

// funcModule.js
const { sayHello, name } = require('{파일위치}');
console.log(`${name} 님 ${sayHello}`)

Single Thread, Event Driven, Non-Blocking I/O

Single Thread, Event Driven, Non-Blocking I/O를 간단히 정리하면 아래와 같습니다.

  • 싱글 쓰레드란?

우선 자바스크립트의 경우 싱글 쓰레드로 처리하는 방식을 채택하고 있습니다. 실제로 처리해야 할 일은 많은데 한번에 하나의 일만 하고 있다는 것으로 생각하면 자바스크립트는 왜 멀티 쓰레드 방식이 아닐까에 대해서는 고민이 자연스럽게 됩니다. 추측으로는 OS 운영체제의 자원을 할당하고 관리하는 부분에서 자바스크립트의 태스크에 대한 제어가 비동기 처리 방식으로 인해 어렵지 않을까 생각됩니다. Node.JS 는 이러한 싱글쓰레드의 단점을 멀티쓰레드 방식과 비슷하게 같은 동작을 병렬로 처리할 수 있는 방법들로 개선하고 있습니다 (실제론 멀티 쓰레딩은 아니고 비슷하게 따라했다고 생각하면 될 것 같습니다)

  • 이벤트 드리븐이란?

위에서 설명한 개념 중 스택과 이벤트 루프 그리고 태스크 큐에 대한 개념에서 이벤트 드리븐은 내가 서비스하고 있는 하나의 사이트를 통해 기능별로 등록된 리스너들입니다. 위의 코드 처럼 콘솔로 바로 실행 이후 종료되는 프로그램이 아니라 언제 누가 내가 만든 사이트에 들어올지 모르는 상황에서 대기를 하고 있다는 것이죠. 이 때 이벤트별로 기능이 정의가 되게 되는데요. 예를 들어 방문했을 때 text/html contentType으로 사용자에게 페이지를 보여준다던지 게시물에 좋아요 버튼을 눌렀을 때 결과를 json 형식으로 클라이언트에게 보내줘서 사용자의 액션이 제대로 수행이 되었는지 사용자한테 알려줘야 겠지요. 이때 모든 일련의 이벤트들의 동작을 정의하고 등록된 상태가 이벤트 리스너에 등록된 상태입니다.

  • Non-Blocking I/O 란?

자바스크립트는 기본적으로 싱글쓰레드 방식을 채택중입니다. 이 때 비동기적 처리(Asyncronous Processing)의 태스크들을 호출 스택에서 태스크 큐로 보내거나 태스크 큐로 부터 이벤트 루프를 통해 다시 스택으로 가져오는 I/O의 형태를 Non-Blocking 이라고 하죠. 이에 따라 실행 순서에 영향을 미치는 행위를 Non-Blocking I/O 라고 간단히 말할 수 있을 것 같습니다. 반대로 Blocking 의 경우 동기적 처리(Syncronous Processing)들에 대해 뒤에 작업들이 해당 작업으로 인해 지연되는 현상을 이야기 합니다.

Node.js Internal Object

브라우저에서 내장객체가 window 객체가 있습니다. 그렇기 때문에 우리가 window를 생략하고 alert, confirm, setTimeout, href 등 내장 객체들을 사용할 수 있죠. 하지만 이 window 객체는 Node.JS 는 존재하지 않습니다. 다른 내장 객체가 존재하는데요. 바로 global 이라는 객체입니다. global은 이름을 명시적으로 사용해도 되지만 생략을 해도 가능합니다.

위의 내장객체를 사용할 때는 global을 생략하거나 직접 명시적으로 붙일 수 있습니다. 예를들어, 우리가 잘 알고 있는 setTimeout을 사용하면 아래와 같이 사용가능 합니다.

// global을 앞에다 붙일 수 있음
global.setTimeout(()=>{
  console.log(`say hello`)
},300)
// global을 생략하면 앞에 global이 있다는 것을 전제로 함
setTimeout(() => {
  console.log(`say hello`)
}, 300)

Browser 의 내장객체인 window 객체와 비교해서 생각하면 편할 것입니다.

console log Object

프로그래밍을 하다보면 디버깅에 대한 부분이 필수적입니다. 자바스크립트에서 디버깅하기 위해서 console 객체를 많이 사용했을 것이라 생각됩니다. 아래와 같이 console 객체가 지니고 있는 함수들의 목록은 아래와 같습니다. 실제로 dir, error, log, info 등을 대표적으로 사용하지만 그 외 많은 것들이 있습니다.

 

 

global 객체의 타이머 함수들

global 객체에 존재하는 타이머 함수들을 잠깐 살펴보면 아래와 같습니다.

  • setTimeout
  • setInterval
  • setImmediate
// 3초 후에 실행
const timeout = setTimeout(() => {
   console.log('Function #1')
},3000)
// 3초 마다 실행함
const interval = setInterval(() => {
   console.log(`Function #2`)
}, 3000)
console.log('setImmediate 전')
// 이벤트 루프로 보낼때 비동기로 할 때 사용함
const immediate = setImmediate(() => {
   console.log(`setImmediate() 즉시 실행`)
})
console.log('setImmediate 후')
setTimeout(() => {
   console.log(`현 시간부로 타임함수는 종료됨...`)
   clearTimeout(timeout)
   clearInterval(interval)
   clearImmediate(immediate)
}, 7000)

위의 코드를 실행 했을 때의 결과는 어떨까요? 바로 아래와 같습니다.

 

setImmediate 함수는 바로 실행되는 함수입니다. 단, 비동기적으로 위에서 배웠던 이벤트 루프를 통해서 태스크 큐에 등록 되었다가 스택으로 불러와 실행하는 함수죠. 그래서 console.log 에 있는 내용들이 먼저 찍히고 나중에 setImmediate 함수가 실행이 됩니다.

setInterval은 정해진 시간인 3초마다 함수를 실행합니다. 취소하기 위해선 setInterval 시 리턴하는 객체 넘버를 통해 clearInterval 함수로 취소할 수 있습니다.

위의 타이머 함수들은 대부분 비동기적인 처리로 호출 스택에서 바로 처리하는 것이 아니라 이벤트 루프가 태스크 큐를 통해 관리하다가 호출 스택으로 보내는 방식으로 처리가 됩니다. 이 부분을 잘 머릿속으로 기억해야 나중에 자바스크립트 디버깅을 할 때 쉽습니다.

 

[출처 : https://medium.com/day34/node-js-%EB%85%B8%EB%93%9C%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%96%B4%EB%96%A0%ED%95%9C-%EA%B8%B0%EB%8A%A5%EB%93%A4%EC%9D%B4-%EC%9E%88%EB%8A%94%EA%B0%80-1-98e49e1100ab]

'Node.JS' 카테고리의 다른 글

[GraphQL] PayloadTooLargeError 에러 해결 방법  (0) 2021.12.31