경일게임아카데미 멀티 디바이스 메타버스 플랫폼 개발자 양성과정 20220907 2022/04/04~2022/12/14

Jinho Lee·2022년 9월 7일
0

2022.09.07 경일 메타버스 23주차 3일 수업내용. Node.js - 자바스크립트 ES2015+, 노드 기능

자바스크립트

  • 자료 : 교과서 “Node.js 교과서” ch. 2 알아두어야 할 자바스크립트 p. 65

프로미스 (Promise)

  • p. 75 ~ 78

  • ES2015부터 자바스크립트와 노드의 API들이 콜백 대신 프로미스 기반으로 재구성됨

    • 콜백 지옥 (Callback Hell) 현상 극복

    • 콜백 지옥 :
      콜백 지옥은 JavaScript를 이용한 비동기 프로그래밍시 발생하는 문제로서, 함수의 매개 변수로 넘겨지는 콜백 함수가 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상을 말한다.

    • 참고 : https://velog.io/@seul06/JavaScript-콜백-지옥

  • 예시 코드 1

    const condition = true; // true면 resolve, false면 reject
    const promise = new Promise((resolve, reject) => {
    if (condition) {
      resolve('성공');
    } else {
      reject('실패');
    }
    });
    // 다른 코드가 들어갈 수 있음
    promise
    .then((message) => {
    console.log(message);   // 성공(resolve)한 경우 실행
    })
    .catch((error) => {
    console.error(error);   // 실패(reject)한 경우 실행
    })
    .finally(() => {            // 끝나고 무조건 실행
    console.log('무조건');
    });
    1. new Promise로 프로미스를 생성할 수 있다.

    2. 프로미스의 내부에 resolve와 reject를 매개변수로 갖는 콜백 함수를 넣는다.

    3. 프로미스인 객체 promise에 then과 catch 메소드를 붙일 수 있다.

    4. 프로미스 내부에서 resolve가 호출되면 then이 실행되고, reject가 호출되면 catch가 실행된다.

    5. finally 부분은 성공 / 실패 여부와 상관없이 실행된다.

  • 프로미스를 간단히 설명하자면, 실행은 바로 하되 결괏값은 나중에 받는 객체

    • 결괏값은 실행이 완료된 후 then이나 catch 메소드를 통해 받는다.

    • 위 예제에서 new Promise는 바로 실행 → 결괏값은 then을 붙였을 때 받는다
      비동기

  • then이나 catch에 다시 다른 then이나 catch를 붙일 수 있다.

    • 이전 then의 return 값은 다음 then의 매개변수로 넘긴다.

      • 프로미스를 return → 수행된 후 다음 then이나 catch 호출
  • 예시 코드 2

    const condition = true; // true면 resolve, false면 reject
    const promise = new Promise((resolve, reject) => {
    if (condition) {
      resolve('성공');
    } else {
      reject('실패');
    }
    });
    
    // then이나 catch에서 다시 다른 then이나 catch를 붙일 수 있다.
    promise
    .then((message) => {
    return new Promise((resolve, reject) => {
      resolve(message);       // 다음 then의 매개변수로 리턴 값을 넘긴다.
    });
    })
    .then((message2) => {
    console.log(message2);
    return new Promise((resolve, reject) => {
      resolve(message2);      // 다음 then의 매개변수로 리턴 값을 넘긴다.
    });
    })
    .then((message3) => {
    console.log(message3);      // 성공(resolve)한 경우 실행
    })
    .catch((error) => {
    console.error(error);       // 3개의 promise 어디서든 실패(reject)하면 실행
    });
    • then에서 new Promise를 return해야 다음 then에서 받을 수 있다.

    • 이를 활용해 콜백을 프로미스로 바꿀 수 있다.

  • 예시 코드 3 - 1

    // 콜백 패턴
    function findAndSaveUser(Users) {
    Users.findOne({}, (err, user) => {  // 첫 번째 콜백
      if (err) {
        return console.error(err);
      }
      user.name = 'zero';
      user.save((err) => {            // 두 번째 콜백
        if (err) {
          return console.error(err);
        }
        Users.findOne({ gender: 'm' }, (err, user) => { // 세 번째 콜백
          // 생략
        });
      });
    });
    }
    
    • 예시 코드의 위는 콜백 함수가 세 번 중첩되어 있다.

      1. 콜백 함수가 나올 때마다 코드의 깊이가 깊어진다.

      2. 각 콜백 함수마다 에러를 따로 처리해야 한다.

    • 이를 아래 코드처럼 프로미스로 바꾸었다.

  • 예시 코드 3 - 2

    // 프로미스 사용
    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);
    });
    }
    1. 코드의 깊이가 깊어지지 않는다.

    2. 마지막 catch에서 에러를 한 번에 처리한다.

  • 프로미스의 사용new Promise함수 내부에 구현되어 있어야 한다.

  • 프로미스 여러 개를 한 번에 실행할 수 있다.

    • Promise.all
  • 예시 코드 4

    const promise1 = Promise.resolve('성공1');
    const promise2 = Promise.resolve('성공2');
    Promise.all([promise1, promise2])
    .then((result) => {
    console.log(result);    // ['성공1', '성공2'];
    })
    .catch((error) => {
    console.error(error);
    });
    • Promise.resolve : 즉시 resolve하는 프로미스를 생성

      • Promise.reject : 즉시 reject하는 프로미스를 생성
    • 프로미스가 여러 개 있을 때 Promise.all에 넣으면 모두 resolve 될 때까지 기다렸다가 then으로 넘어간다.

      • result 매개변수에 각각의 프로미스 결괏값이 배열로 들어간다.

      • 하나라도 reject 되면 catch로 넘어간다.

async/await

  • p. 78 ~ 81

  • 노드 7.6 버전, ES2017부터 추가되고 지원되는 기능

  • 비동기 위주 프로그래밍에서 도움이 많이 된다.

  • 프로미스를 사용한 코드를 한 번 더 줄일 수 있다.

  • 예시 코드 5 - 1

    // 예시 코드 3 - 2
    // 프로미스 사용
    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/await 문법을 사용해 더 줄일 수 있다.

      • async function이 추가되었다.
  • 예시 코드 5 - 2

    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될 때까지 기다린 후 다음 로직으로 넘어간다.

      • ex. await Users.findOne({})이 resolve될 때까지 기다린 후 user 변수를 초기화

    • 위 예시에는 에러를 처리하는 부분이 없다. (프로미스가 reject된 경우)

  • 예시 코드 5 - 3

    async function findAndSaveUser(Users) {
    try {
      let user = await Users.findOne({});
      user.name = 'zero';
      user = await user.save();
      user = await Users.findOne({ gender: 'm' });
      // 생략
    } catch (error) {
      console.error(error);
    }
    }
    • try/catch 문으로 로직을 감싸,
      프로미스의 catch 메소드처럼 try/catch 문의 catch가 에러를 처리

    • 화살표 함수도 async와 같이 사용할 수 있다.

  • 예시 코드 5 - 4

    const findAndSaveUser = async (Users) => {
    try {
      let user = await Users.findOne({});
      user.name = 'zero';
      user = await user.save();
      user = await Users.findOne({ gender: 'm' });
      // 생략
    } catch (error) {
      console.error(error);
    }
    };
  • 프로미스를 순차적으로 실행하는 방법

    • 노드 10부터 지원하는 ES2018 문법

    • for문과 async/await을 사용

    • for await of 문을 사용해 프로미스 배열을 순회한다.

  • 예시 코드 5 - 5

    const promise1 = Promise.resolve('성공1');
    const promise2 = Promise.resolve('성공2');
    (async () => {
    for await (promise of [promise1, promise2]) {
      console.log(promise);
    }
    })();
    • async 함수의 반환값은 항상 Promise로 감싸진다.

      • new Promise로 then 메소드의 반환값을 프로미스로 만들듯이
        반환값이 항상 프로미스 객체라는 뜻

      • 따라서 실행 후 then을 붙이거나 또 다른 async 함수 안에서 await을 붙여 처리할 수 있다.

  • 예시 코드 5 - 6

    async function findAndSaveUser(Users) {
    // 생략
    }
    
    findAndSaveUser().then(() => { /* 생략 */ });
    // 또는
    async function other() {
    const result = await findAndSaveUser();
    }

노드 기능

  • 자료 : 교과서 “Node.js 교과서” ch. 3 노드 기능 알아보기 p. 89

REPL

  • p. 90 ~ 91

  • 자바스크립트는 스크립트 언어이다.
    ⇒ 미리 컴파일을 하지 않아도 즉석에 코드 실행 가능

  • REPL (Rad Eval Print Loop) :
    노드 제공 콘솔.
    입력한 코드를 읽고 (Read), 해석하고 (Eval), 결과물을 반환하고 (Print), 종료할 때까지 반복한다 (Loop)

모듈

  • p. 92 ~ 96

  • 노드는 코드를 모듈로 만들 수 있다.

  • 모듈 :
    특정한 기능을 하는 함수나 변수들집합

    • 하나의 프로그램이면서 다른 프로그램의 부품

    • 재사용할 수 있다.

  • 모듈 만들기

    • module.exports에 변수들을 담은 객체를 대입하여 파일을 모듈로 만든다.

    • require 함수 안에 불러올 모듈의 경로를 적어 참조한다.

  • 예시 코드 6

    // var.js
    const odd = '홀수입니다';
    const even = '짝수입니다';
    
    module.exports = {
    odd,
    even,
    };
    
    // func.js
    const { odd, even } = require('./3_3_var');
    
    function checkOddOrEven(num) {
    if (num % 2) {  // 홀수면
      return odd;
    }
    return even;
    }
    
    module.exports = checkOddOrEven;
    
    **// index.js
    const { odd, even } = require('./3_3_var');
    const checkNumber = require('./3_3_func');
    
    function checkStringOddOrEven(str) {
    if (str.length % 2) {   // 홀수면
      return odd;
    } 
    return even;
    }
    
    console.log(checkNumber(10));
    console.log(checkStringOddOrEven('hello'));
  • 모듈의 장단점

    • 장점 :
      여러 파일에 걸쳐 재사용되는 함수나 변수를 모듈로 만들어두면 편리하다.

    • 단점 :
      모듈이 많아지고 모듈 간의 관계가 얽히게 되면 구조를 파악하기 어렵다.

노드 내장 객체

  • p. 96 ~ 112

global

  • p. 96 ~ 98

  • 전역 객체

    • 모든 파일에서 접근 가능

    • 생략 가능

    • 파일 간에 간단한 데이터를 공유할 때 사용하기도 한다.

  • 남용에 주의

    • 어떤 파일에서 global 객체에 값을 대입했는지 찾기 힘들어져 유지 보수가 어려워진다.

console

  • p. 98 ~ 100

  • 로깅 함수

    1. console.time(레이블) :
      console.timeEnd(레이블)과 대응되어 같은 레이블을 가진 time과 timeEnd 사이의 시간을 측정

      • 레이블 : 시간을 구분하기 위해 붙은 이름, 문자열
    2. console.log(내용) :
      로그를 콘솔에 표시.
      console.log(내용, 내용, 내용, …)처럼 사용해 여러 내용을 동시에 표시할 수도 있다.

    3. console.error(에러 내용) :
      에러를 콘솔에 표시.

    4. console.table(배열) :
      배열의 요소로 객체 리터럴을 넣으면 객체의 속성들을 테이블 형식으로 표시.

    5. console.dir(객체, 옵션 객체) :
      객체를 콘솔에 표시.
      옵션의 colors는 콘솔에 색을 추가할지 결정.
      옵션의 depth는 객체 안의 객체를 몇 단계까지 보일지를 결정. 기본값은 2.

    6. console.trace(레이블) :
      에러가 어디서 발생했는지 추적.
      에러의 위치가 에러와 함께 나오지 않는다면 사용

타이머

  • p. 101 ~ 103

  • 노드에서 타이머 기능을 제공하는 함수는 global 객체 안에 들어있다.

  • 타이머 기능 함수

    • 타이머 함수들은 모두 아이디를 반환하며, 이를 사용해 취소할 수 있다.
    1. setTimeout(콜백 함수, 밀리초) :
      주어진 밀리초 이후에 콜백 함수를 실행

    2. setInterval(콜백 함수, 밀리초) :
      주어진 밀리초 마다 콜백 함수를 반복 실행

    3. setImmediate(콜백 함수) :
      콜백 함수를 즉시 실행

    4. clearTimeout(아이디) :
      setTimeout 취소

    5. clearInterval(아이디) :
      setInterval 취소

    6. clearImmediate(아이디) :
      setImmediate 취소

  • 예시 코드 7

    const timeout = setTimeout(() => {
    console.log('1.5초 후 실행');
    }, 1500);
    
    const interval = setInterval(() => {
    console.log('1초마다 실행');
    }, 1000);
    
    const timeout2 = setTimeout(() => {
    console.log('실행되지 않습니다');
    }, 3000);
    
    setTimeout(() => {
    clearTimeout(timeout2);
    clearInterval(interval);
    }, 2500);
    
    const immediate = setImmediate(() => {
    console.log('즉시 실행');
    });
    
    const immediate2 = setImmediate(() => {
    console.log('실행되지 않습니다');
    });
    
    clearImmediate(immediate2);
  • 결과 :

    즉시 실행
    1초마다 실행
    1.5초 후 실행
    1초마다 실행

setImmediate(Callback)과 setTimeout(Callback, 0)

  • setImmediate(Callback)과 setTimeout(Callback, 0)에 담긴 콜백 함수는 이벤트 루프를 거친 뒤 즉시 실행된다.

  • I/O 작업의 콜백 함수 안에서 타이머를 호출하는 경우,
    setImmediate가 setTimeout(콜백, 0)보다 먼저 실행된다.

    • 다만 항상 먼저 호출되지는 않으며,
      따라서 헷갈리지 않도록 setImmediate만 사용하자.

filename, dirname

  • p. 103

  • filename, dirname 키워드는 경로 정보를 제공한다.

    • 파일에 filename, dirname을 넣어두면 실행 시 현재 파일명, 파일 경로로 바뀐다.
  • 경로 구분자 문제 등을 해결하기 위해 보통 path 모듈과 함께 쓴다.

module, exports, require

  • p. 103 ~ 108

  • 모듈을 만들 때, module.exports 말고 exports 객체도 사용 가능하다.

    • 각각의 변수를 exports 객체에 하나씩 넣을 수 있다.

    • module.exports와 exports가 같은 객체를 참조하기 때문

  • 노드에서의 this

    • 최상위 스코프의 thismodule.exports (또는 exports)

    • 함수 선언문 내부의 thisglobal 객체

require

  • 자바스크립트에서 함수는 객체

  • require의 속성

    • require.cache :
      파일 이름속성명으로 들어있다.
      속성값으로는 각 파일의 모듈 객체

      • 한 번 require한 파일은 require.cache에 저장되므로 다음 번 require할 때 새로 불러오지 않고 require.cache에 있는 것이 재사용된다.
    • require.main :
      노드 실행 시 첫 모듈
      노드를 실행한 파일

      • require.main 객체의 모양은 require.cache의 모듈 객체와 같다.

      • 현재 파일이 첫 모듈인지 알아보는 방법

        require.main === module
        // 첫 모듈이면 true
        • 첫 모듈의 이름 : require.main.filename

순환 참조(Circular Dependency)

  • 두 모듈이 서로 참조(require)하면 발생

  • 순환 참조되는 대상을 빈 객체(null)로 만든다.

    • 에러가 발생하지 않기에 예기치 못한 동작이 발생 가능

0개의 댓글