본문 바로가기

WINK-(Web & App)/Express.js (Node.js) 스터디

[2024 여름방학 Node.js 스터디] 이종윤 #1주차

반응형

서버

: 서버(server)는 클라이언트에게 네트워크를 통해 정보나 서비스를 제공하는 컴퓨터 시스템으로 컴퓨터 또는 프로그램을 말한다.  (* 클라이언트란 요청을 보내는 주체이다)

클라이언트는 브라우저일 수도 있고, 데스크톱 프로그램일 수도 있고, 모바일 앱일 수도 있고, 다른 서버에 요청을 보내는 서버일 수도 있다. 이처럼 서버는 요청에 대한 응답만을 하는 것이 아니라 클라이언트가 되어 다른 서버에 요청을 보낼 수도 있다. 또한 서버는 클라이언트에게 요청을 받았을 때 항상 Yes라고만 응답을 하지않고 클라이언트의 자격을 보고 Yes가 아닌 No를 보낼 수 있다.

서버와 클라이언트의 관계

자바스크립트의 런타임

먼저 Node.js 의 정의를 살펴보면 Node.js는 Chrome V8 Javascript 엔진으로 빌드된 자바스크립트 런타임입니다.

간단히 말해 Node.js는 javascript의 런타임이다. 여기서 런타임은 특정 언어로 만든 프로그램들을 실행할 수 있는 환경을 의미한다. 

그러므로 Node.js를 공부하기전에 기본적인 자바스크립트 문법에 대한 학습이 선행되어야한다.

노드는 V8엔진과 더불어 libuv라는 라이브러리를 사용한다. ( V8 웹 브라우저를 만드는 데 기반을 제공하는 오픈 소스 자바스크립트 엔진이다. 구글 크롬 브라우저와 안드로이드 브라우저에 탑재되어 있다. _위키백과)

libuv 라이브러리는 노드의 특성인 이벤트 기반, 논블로킹 I/O 모델을 구현하고 있다.

노드의 구조

이벤트기반

이벤트 기반(event-driven)이란 이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 방식을 의미한다. 이벤트로는 클릭이나 네트워크 요청 등이 있을 수 있다.

이벤트 기반 시스템에서는 특정 이벤트가 발생할 때 무엇을 할지 미리 등록해둬야 한다. 이를 이벤트 리스너(event listener)에 콜백(callback) 함수를 등록한다고 표현한다.

이벤트기반 작동 원리

위 그림처럼 이벤트 리스너에 콜백 함수를 미리 등록해 두고 이벤트가 발생하면 등록된 콜백함수가 실행되는 구조이다.

 

노드는 js 코드의 맨 위부터 한줄씩 실행한다. 함수호출 부분을 발견했다면 호출한 함수를 호출 스택(call stack)에 넣는다. (호출 스택은 여러 함수들을 호출하는 스크립트에서 해당 위치를 추적하는 인터프리터를 위한 메커니즘이다. 현재 어떤 함수가 실행중인지, 그 함수 내에서 어떤 함수가 호출되어야 하는지, 등을 제어한다.

자바스크립트 코드가 실행되면 anonymous 함수라는 것이 처음에 호출스택에 들어가게된다. anonymous 함수는 처음 실행 시의 전역 콘텍스트(global context)이다. 콘텍스트는 함수가 호출되었을 때 생성되는 환경을 의미한다. 자바스크립트 코드는 실행 시 기본적으로 전역 콘텍스트 안에서 돌아간다고 생각하는 게 좋다. 함수는 실행되는 동안 호출 스택에 머물러 있다가 실행이 완료되면 호출 스택에서 지워진다.

 

 이벤트 루프: 이벤트 기반 모델에서는 이벤트 루프(event loop)라는 개념이 등장한다. 여러 이벤트가 동시에 발생했을 때 어떤 순서로 콜백 함수를 호출할지를 판단하는 것이 바로 이벤트 루프(event loop)이다.

 백그라운드: setTimeout 같은 타이머나 이벤트 리스너들이 대기하는 곳입니다. 자바스크립트가 아닌 다른 언어로 작성된 프로그램이라고 봐도 무방하며, 여러 작업이 동시에 실행될 수 있습니다.

 태스크 큐: 이벤트 발생 후, 백그라운드에서는 태스크 큐로 타이머나 이벤트 리스너의 콜백 함수를 보냅니다. 정해진 순서대로 콜백들이 줄을 서 있으므로 콜백 큐라고도 합니다. 콜백들은 보통 완료된 순서대로 줄을 서 있지만, 특정한 경우 순서가 바뀌기도 합니다.

논블로킹 I/O

이벤트 루프를 잘 활용하면 오래 걸리는 작업을 효율적으로 처리할 수 있다. 작업에는 두 가지 종류가 있는데, 동시에 실행될 수 있는 작업과 동시에 실행될 수 없는 작업이다. 기본적으로 여러분이 작성한 자바스크립트 코드는 동시에 실행될 수 없다. 하지만 자바스크립트상에서 돌아가는 것이 아닌 I/O 작업 같은 것은 동시에 처리될 수 있다.

I/O는 입력(Input)/출력(Output)을 의미한다. 파일 시스템 접근(파일 읽기 및 쓰기, 폴더 만들기 등)이나 네트워크를 통한 요청 같은 작업이 I/O의 일종이다. 이러한 작업을 할 때 노드는 논블로킹 방식으로 처리하는 방법을 제공합니다.

논블로킹(non-blocking)이란 이전 작업이 완료될 때까지 대기하지 않고 다음 작업을 수행하는 것을 의미합니다. 반대로 블로킹(blocking)은 이전 작업이 끝나야만 다음 작업을 수행하는 것을 의미합니다.

논 블로킹의 중요성

위 그림을 보면 논블로킹 방식의 중요성을 알 수 있다. 블로킹방식으로 처리한 1번의 경우 5초가 소요되지만 논 블로킹 방식으로 2번처럼 처리하면 동일한 작업이 약 3초가 소요된다.

이것이 우리가 논 블로킹 방식으로 코딩해야하는 이유이다.

논 블로킹 방식으로 코딩하는 방법

오래 걸리는 작업을 setTimeout(콜백, 0)코드를 사용하여 논블로킹으로 만드는 것이다.

다만, 아무리 논블로킹 방식으로 코드를 작성하더라도 코드가 전부 내가 작성한 것이라면 전체 소요 시간이 짧아지지는 않는다. 내가 짠 코드는 I/O 작업이 아니라 서로 동시에 실행되지 않기 때문에 단순히 실행 순서만 바뀔 뿐이다. 그러나 I/O 작업이 없다고 해서 논블로킹이 의미가 없는 것은 아니다. 오래 걸리는 작업을 처리해야 하는 경우, 논블로킹을 통해 실행 순서를 바꿔줌으로써 그 작업 때문에 간단한 작업들이 대기하는 상황을 막을 수 있다는 점에서 의의가 있다.

또한, 논블로킹 동시가 같은 의미가 아니라는 사실을 알아야한다. 동시성은 동시 처리가 가능한 작업을 논블로킹 처리해야 얻을 수 있는 것이다.

싱글 스레드

싱글 스레드란 스레드가 하나뿐이라는 것을 의미한며 작성한 자바스크립트 코드가 동시에 실행될 수 없는 이유이다.

우선 스레드를 이해하기 위해서는 프로세스부터 알아야 한다.

 

 프로세스는 운영체제에서 할당하는 작업의 단위입니다. 노드나 웹 브라우저 같은 프로그램은 개별적인 프로세스입니다. 프로세스 간에는 메모리 등의 자원을 공유하지 않습니다.

 스레드는 프로세스 내에서 실행되는 흐름의 단위입니다. 프로세스는 스레드를 여러 개 생성해 여러 작업을 동시에 처리할 수 있습니다. 스레드들은 부모 프로세스의 자원을 공유합니다. 같은 주소의 메모리에 접근 가능하므로 데이터를 공유할 수 있습니다.

프로세스와 스레드

그러니까 윈도우라는 운영체제에서 실행되는 Node가 하나의 프로세스이고 Node라는 프로세스는 싱글스레드이기때문에 작성된 js 코드는 하나씩밖에 실행되지 않는다.

그러나 사실 노드는 싱글스레드는 아니다. 우선  노드를 실행하면 먼저 프로세스가 하나 생성된다. 그리고 그 프로세스에서 스레드들을 생성하는데, 이때 내부적으로 스레드를 여러 개 생성한다. 그중에서 직접 제어할 수 있는 스레드는 하나뿐이다. 그래서 흔히 노드가 싱글 스레드라고 여겨지는 것이다.

생각해보면 여러개의 일을 동시에 처리할 수 있는 멀티스레드가 더 좋아보일 수 있다. 그러나 멀티스레드는 할 일이 몇개 없을 때는 노는 스레드가 생겨 효율적이지 않고 프로그래밍 하기 어렵다는 단점이 있다.

그러므로 각 사용처에 맞는 방식을 선택해 코딩을 하는 것이 좋다.

멀티스레드 싱글스레드
하나의 프로세스 안에서 여러 개의 스레드 사용 여러 개의 프로세스 사용
CPU 작업이 많이 사용될 때 사용 I/O 요청이 많을 때 사용
프로그래밍이 어려움 비교적 쉬움

서버로서 노드

노드는 기본적으로 싱글 스레드, 논블로킹 모델을 사용하므로 노드서버 또한 같다. 따라서 노드 서버의 장단점은 싱글 스레드, 논블로킹 모델의 장단점과 크게 다르지 않습니다.

 

장점: 서버에는 기본적으로 I/O 요청이 많이 발생하므로, I/O 처리를 잘하는 노드를 서버로 사용하면 좋다. 노드는 libuv 라이브러리를 사용해 I/O 작업을 논블로킹 방식으로 처리한다. 따라서 스레드 하나가 많은 수의 I/O를 혼자서도 감당할 수 있기에 매우 효율적이다.

단점: 노드는 CPU 부하가 큰 작업에는 적합하지 않다. 작성된 코드는 모두 스레드 하나에서 처리되므로 코드가 CPU 연산을 많이 요구하면 스레드 하나가 혼자서 감당하기 어렵다.

 

그러므로 노드는 개수는 많지만 크기는 작은 데이터를 실시간으로 주고받는 데 적합하다. (like 네트워크나 데이터베이스, 디스크 작업 )

장점 단점
멀티 스레드 방식에 비해 적은 컴퓨터 자원 사용 기본적으로 싱글 스레드라서 CPU 코어를 하나만 사용
I/O 작업이 많은 서버로 적합 CPU 작업이 많은 서버로는 부적합
멀티 스레드 방식보다 쉬움 하나뿐인 스레드가 멈추지 않도록 관리 필요
웹 서버가 내장되어 있음 서버 규모가 커졌을 때 서버를 관리하기 어려움
자바스크립트를 사용함 어중간한 성능
JSON 형식과 쉽게 호환됨

노드 설치하기

노드는 6개월마다 버전이 1씩 올라가니 최신 버전을 다운 받으면 된다. 또한 짝수버전을 다운 받는 것을 추천한다.

윈도우와 맥은 GUI를 사용하므로 웹 페이지에 들어가서 그냥 다운받으면 된다.

 

 

위에서 Node.js를 잘 사용하기 위해서는 기본적으로 js문법이 선행되어야 한다고 했다.

js문법을 배워보자 ~

 

JS문법

자바스크립트는 매년 새로운 버전으로 업데이트된다. 노드도 주기적(6개월마다)으로 버전을 올리며 변경된 자바스크립트 문법을 반영하고 있다.

const, let

우선 위 두개와 var 모두 변수를 선언하는데 쓰이는 문법이다.

var의 범위는 함수스코프로 블록과 관계없이 사용할 수 있지만 const와 let는 범위를 블록스코프로 가지므로 가지므로 블록 밖의 변수에는 접근할 수 없다.(블록의 범위는 '{}'(중괄호)이다.)

const: 한 번 선언하면 값을 바꿀 수가 없다.(흔히 상수라고 부른다.)

let: 한 번 선언해도 값을 바꿀 수 있다.

템플릿 문자열

: 문자열 사이에 변수를 더 편하게 넣기 위해 `(백틱)을 사용한다.

var num1 = 1;
var num2 = 2;
var result = 3;
var string1 = num1 + ' 더하기 '  + num2 + '는 \'' + result + '\'';
console.log(string1); // 1 더하기 2는 '3'

를 `을 사용하면

const num3 = 1;
const num4 = 2;
const result2 = 3;
const string2 = `${num3} 더하기 ${num4}는 ' ${result2}'`;
console.log(string2); // 1 더하기 2는 '3'

로 간단하게 나타낼 수 있다.

객체 리터럴

: 객체의 메서드에 함수를 연결할 때 더는 ':'과 function 을 붙이지 않아도 된다

{ name: name, age: age } // ES5
{ name, age } // ES2015

위에 모양에서 아래의 모양으로 편하게 바뀌었다.

 

화살표 함수

: 원래 쓰던 function 대신 '=>'로 함수 선언 가능하다
 변수에 대입하면 나중에 재사용 가능하다

구조분해 할당

: 구조 분해 할당을 사용하면 객체와 배열로부터 속성이나 요소를 쉽게 꺼낼 수 있다.

이렇게

클래스

js에서 클래스는 다른 언어처럼 클래스 기반으로 동작하는 것이 아니라 프로토타입 기반으로 동작한다. 

 

* 프로토타입이란?

Java, C++과 같은 클래스 기반 객체지향 프로그래밍 언어와 달리 자바스크립트는 프로토타입 기반 객체지향 프로그래밍 언어이다. 따라서 자바스크립트의 동작 원리를 이해하기 위해서는 프로토타입의 개념을 잘 이해하고 있어야 한다.

자바스크립트의 모든 객체는 자신의 부모 역할을 담당하는 객체와 연결되어 있다. 그리고 이것은 마치 객체 지향의 상속 개념과 같이 부모 객체의 프로퍼티 또는 메소드를 상속받아 사용할 수 있게 한다. 이러한 부모 객체를 Prototype(프로토타입) 객체 또는 줄여서 Prototype(프로토타입)이라 한다. 

간단히 __proto__로 상속을 받는 것을 다른 언어들처럼 class를 사용해 보기 좋게 바꿨다.

 

프로미스

: 자바스크립트와 노드에서는 주로 비동기를 접합니다. 특히 이벤트 리스너를 사용할 때 콜백 함수를 자주 사용합니다. ES2015부터는 자바스크립트와 노드의 API들이 콜백 대신 프로미스(Promise) 기반으로 재구성된다.

promise 기본 형태

 

어떠한 코드로 명령을 내릴 때 그 코드가 성공할 수도 있고 실패할 수도 있다.

이럴 때에 .then과 .catch 메서드를 사용해 이 함수가 성공, 실패일 때 행동지침을 줄 수 있다.

async, await

: 프로미스를 사용한 코드를 깔끔하게 만들어준다.

function findAndSaveUser(Users) {
  Users.findOne({})
    .then((user) => {
      user.name = 'zero';
      return user.save();
    })
    .then((user) => {
      return Users.findOne({ gender: 'm' });
    })
    .then((user) => {
      // 생략
    })
    .catch(err => {
      console.error(err);
    });
}

이러한 코드를

async function findAndSaveUser(Users) {
  let user = await Users.findOne({});
  user.name = 'zero';
  user = await user.save();
  user = await Users.findOne({ gender: 'm' });
  // 생략
}

이렇게 바꿔준다.

함수 선언부를 일반 함수 대신 async function으로 교체한 후, 프로미스 앞에 await을 붙여 사용한다.

함수는 해당 프로미스가 resolve될 때까지 기다린 뒤 다음 로직으로 넘어간다.

Map, Set

: Map은 속성들 간의 순서를 보장하고 반복문을 사용할 수 있고 속성명으로 문자열이 아닌 값도 사용할 수 있고 size 메서드를 통해 속성의 수를 쉽게 알 수 있다.

 

: Set은 중복을 허용하지 않는다. 따라서 배열 자료구조를 사용하고 싶으나 중복은 허용하고 싶지 않을 때 Set을 대신 사용하면 된다. 또는 기존 배열에서 중복을 제거하고 싶을 때도 Set을 사용하면 된다.

const arr = [1, 3, 2, 7, 2, 6, 3, 5];

const s = new Set(arr);
const result = Array.from(s);
console.log(result); // 1, 3, 2, 7, , 5

널 병합, 옵셔널 체이닝

??(널 병합(nullish coalescing)) 연산자는 주로 || 연산자 대용으로 사용되며, falsy (0, '', false, NaN, null, undefined)  null undefined만 따로 구분한다.

const a = 0;
const b = a || 3; // || 연산자는 falsy 값이면 뒤로 넘어감
console.log(b); // 3

const c = 0;
const d = c ?? 3; // ?? 연산자는 null과 undefined일 때만 뒤로 넘어감
console.log(d); // 0;

const e = null;
const f = e ?? 3;
console.log(f); // 3;

const g = undefined;
const h = g ?? 3;
console.log(h); // 3;

 

?.(옵셔널 체이닝(optional chaining)) 연산자는 null이나 undefined의 속성을 조회하는 경우 에러가 발생하는 것을 막는다.

const a = {}
a.b; // a가 객체이므로 문제없음

const c = null;
try {
  c.d;
} catch (e) {
  console.error(e); // TypeError: Cannot read properties of null (reading 'd')
}
c?.d; // 문제없음

try {
  c.f();
} catch (e) {
  console.error(e); // TypeError: Cannot read properties of null (reading 'f')
}
c?.f(); // 문제없음

try {
  c[0];
} catch (e) {
  console.error(e); // TypeError: Cannot read properties of null (reading '0')
}
c?.[0]; // 문제없음

프런트엔드 자바스크립트

AJAX

비동기적 웹 서비스를 개발할 때 사용하는 기법이다. 페이지 이동 없이 서버에 요청을 보내고 응답을 받는 기술이다.

 

GET 요청에서는 axios.get 함수의 인수로 요청을 보낼 주소를 넣으면 된다. 프로미스를 써서 then과 catch를 쓴다.

axios.get('https://www.zerocho.com/api/get')
  .then((result) => {
    console.log(result);
    console.log(result.data); // {}
  })
  .catch((error) => {
    console.error(error);
});

 

POST 요청에서는 데이터를 보낼 수 있다.

(async () => {
  try {
    const result = await axios.post('https://www.zerocho.com/api/post/json', {
      name: 'zerocho',
      birth: 1994,
    });
    console.log(result);
    console.log(result.data);
  } catch (error) {
    console.error(error);
  }
})();

FormData

: HTML form 태그의 데이터를 동적으로 제어할 수 있는 기능이다. 주로 AJAX와 함께 사용된다.

 

(async () => {
  try {
    const formData = new FormData();
    formData.append('name', 'zerocho');
    formData.append('birth', 1994);
    const result = await axios.post('https://www.zerocho.com/api/post/formdata', formData);
    console.log(result);
    console.log(result.data);
  } catch (error) {
    console.error(error);
  }
})();

encodeURIComponent, decodeURIComponent

: AJAX 요청을 보낼 때, ‘http://localhost:4000/search/노드’처럼 주소에 한글이 들어가는 경우가 있다. 서버 종류에 따라 다르지만 서버가 한글 주소를 이해하지 못하는 경우가 있는데, 이럴 때 window 객체의 메서드인 encodeURIComponent 메서드를 사용한다.

(async () => {
  try {
    const result = await axios.get(`https://www.zerocho.com/api/search/${encodeURIComponent('노드')}`);
    console.log(result);
    console.log(result.data); // {}
  } catch (error) {
    console.error(error);
  }
})();

데이터 속성과 dataset

데이터 속성(data attribute): HTML과 관련된 데이터를 저장하는 공식적인 방법

<ul>
  <li data-id="1" data-user-job="programmer">Zero</li>
  <li data-id="2" data-user-job="designer">Nero</li>
  <li data-id="3" data-user-job="programmer">Hero</li>
  <li data-id="4" data-user-job="ceo">Kero</li>
</ul>
<script>
  console.log(document.querySelector('li').dataset);
  // { id: '1', userJob: 'programmer' }
</script>

 

출처: Node.js 교과서 개정 3판

반응형