소파에서 개발하기

[SEB TIL] 26일차 Node.js 모듈 + promise + async 실습 본문

카테고리 없음

[SEB TIL] 26일차 Node.js 모듈 + promise + async 실습

couch 2022. 5. 30. 15:08

과제 Part 1 - 타이머 API

1) 타이머 관련 API

  • setTimeout(callback, millisecond) : 일정 시간 후에 함수를 실행
    • 매개변수 : 실행할 콜백 함수, 실행 전 기다릴 시간(0.001초)
    • return값 : 임의의 타이머 ID
    setTimeout(function () {
      console.log('1초 후 실행');
    }, 1000);
    // 123
    
  • clearTimeout(timerId) : setTimeout 타이머를 종료
    • 매개변수 : 타이머 ID
    • return값 : 없음
    const timer = setTimeout(function () {
      console.log('10초 후 실행');
    }, 10000);
    //timer에 id를 반환받아 저장
    clearTimeout(timer);
    // setTimeout이 종료됨
    
  • setInterval(callback, millisecond) : 일정 시간의 간격을 가지고 함수를 반복적으로 실행
    • 매개변수 : 콜백함수, 반복적으로 실행시킬 시간 간격(0.001초)
    • return값 : 임의의 타이머 ID
  • clearInterval(timerId) : setInterval 타이머를 종료
    • 매개변수 : 타이머 ID
    • return값 : 없음
  • **bind() 는 뭘까?
    • 기본적으로는 setTimeout에 어떤 객체 arr 안에 있던 메서드를 전달할 때 this가 맥락을 잃어 arr → window로 바뀌어 현상을 막고 this를 arr로 고정하는 메서드
    • sleep.bind(null, 2000) ⇒ 여기서는 bind 메서드를 이용해서 sleep에 전달하는 인자 값을 2000으로 고정(참고)

2) Promise 이해하기

참고 : https://ko.javascript.info/promise-basics

  • promise 인스턴스의 맥락
    1. '제작 코드(producing code)'
      • promise에 전달되는 함수(callback)
      • 시간이 걸리는 일을 하는 함수
      • 시간이 얼마나 걸리든 성공/실패 중 1가지의 결과를 만들어 냄.
    2. '소비 코드(consuming code)'
      • '제작 코드’의 결과를 기다렸다가 이를 소비하는 함수
      • (메소드 쓰듯) 여러 개가 줄줄이 사용될 수 있음.
    3. 프라미스(promise)
      • '제작 코드’와 '소비 코드’를 연결해 주는 특별한 자바스크립트 객체
      • ‘제작 코드’가 준비되었을 때 그 결과를 소비 코드가 사용할 수 있도록 전달함.
  • promise 인스턴스 만들기
    let promise = new Promise(function(resolve, reject) {
      // executor (resolve와 reject를 인수로 받는 제작 코드)
    });
    
    • Promise 클래스에 executor(제작 함수)를 전달해 만듦
      • new Promise 시 생성자가 반환하는(상속하는) promise 객체의 내부 프로퍼티 : state, result
    • executor는 new Promise가 만들어질 때 즉각적으로 호출됨 ⇒ 그게 싫다면 실행 조건을 만들거나 setTimeout의 callback으로 주기
    • executor의 인수 resolve와 reject
      • 자바스크립트에서 자체 제공하는 콜백이므로 직접 작성하지 않아도 됨
      • 인수로 넘겨준 콜백 중 하나를 반드시 호출
  • resolve, reject 의미
    • promise의 executor 함수가 성공했을 때 실행할 callback / 실패했을 때 실행할 callback
      • resolve(’hello’)
        : 성공했을 때 [PromiseState] 값을 "pending" → "fulfilled"로, [PromiseResult] 값을 undefined → "hello"로 바꿈
      • reject(error)
        : 실패했을 때 [PromiseState] 값을 "pending" → "failed"로, [PromiseResult] 값을 undefined → error로 바꿈
    • status: "rejected"인 것을 .catch()로 잡으면 status:"fulfilled"로 바뀌어 더 이상 오류가 나지 않음
      <= 어떤 경로든 state 속성값이 한 번 fulfilled로 바뀌면 더 이상 변경되지 않음
    • PromiseResult 속성값을 전달인자로 활용 : .then( [성공result], [실패result] ) 메서드의 callback의 인자로 전달됨
      ** promise와 then이 value를 리턴하는 게 아니라, 속성값이 value로 바뀐 promise 객체를 리턴하면 뒤이은 메서드에서 이 속성값을 받아다 쓰는 것!

  • promise 인스턴스의 메서드
    1. .then( resultFunc, errorFunc )
      promise.then(
        function(result) { /* 결과(result)를 다룹니다 */ },
        function(error) { /* 에러(error)를 다룹니다 */ }
      );
      • 첫 번째 인자로 resolve 시 전달된 value를 처리할 함수를, 두 번째 인자 자리에 rejected 시 전달될 error를 처리할 함수를 받는다
      • resolve만 있고 rejected는 없는 경우 : then( resultFunc )만 적어도 된다
      • rejected만 있고 resolve는 없는 경우 : then( null, errorFunc )과 같이 첫 번째 자리를 채우고 두번째 자리에 errorFunc값을 전달해야 한다
    2. .catch(error)
      • 체인을 진행하다가 에러가 발생하면 발생지점과 catch 사이의 then은 건너뛰고 catch로 가서 에러를 처리
      • .then( null, errorFunc )과 동일
    3. .finally(someFunc)
      • 실행 결과가 resolve인지 rejected인지 상관없이 someFunc를 실행 => 그 다음 then이나 catch로 실행 결과를 전달
      • .then(someFunc, someFunc)와 비슷
  • 잊지말자! POINT!
    •  배열의 .filter()나 .map() 메소드 등이 실행 후 배열을 리턴하기 때문에 계속 덧붙여 활용할 수 있는 것처럼, promise 객체와 then()도 실행 후 Promise 객체를 리턴하기 때문에 메서드를 chaining할 수 있음 (then은 리졸브된 promise 객체 반환)
    • then에서 연산한 결과를 'return'하면 Promise객체의 [[PromiseResult]]에 return 값이 담김. return 안하면 undefined가 됨 => undefined가 리턴되어도 체이닝은 계속 이루어짐
    • await로 실행을 기다리지 않고 promise(arg)를 생으로 변수에 할당하면 resolved data를 할당받는 게 아니라 promise 인스턴스를 할당받게 될 것

3) async / await 이해하기

  • async 키워드
    • await 키워드 다음에 등장하는 함수 실행은 promise를 리턴해야만 의미가 있다!
    • function 앞에 async를 붙이면 해당 함수는 항상 promise 인스턴스를 반환
    • promise가 아닌 값을 반환하는 함수라도 그걸 resolved promise로 감싸서 반환
    async function f() {
      return 1;
    }
    
    f().then(alert); // 1 (결과가 1인 resolved promise)
    
    //명시적으로 프라미스를 반환한 것과 결과가 동일
    async function f() {
      return Promise.resolve(1);
    }
    
  • await 키워드
    // await는 async 함수 안에서만 동작합니다.
    async function f() {
    
      let promise = new Promise((resolve, reject) => {
        setTimeout(() => resolve("완료!"), 1000)
      });
    
      let result = await promise; // 프라미스가 이행될 때까지 기다림 (*)
    
      alert(result); // "완료!"
    }
    
    f();
    • **await는 async가 붙은 함수 안에서만 동작**   ⇒  안 지키면 syntax error
    • 자바스크립트는 await 키워드를 만나면 promise가 처리될 때까지 멈춰서 기다림 ⇒ 그 이후에 결과를 반환하고 실행 재개
      promise(arg)를 생으로 변수에 할당할 때와 다르게 await promise(arg) 를 할당하면 resolve 결과값이 담김 
    • promise 처리를 기다리는 동안에 엔진이 다른 일(다른 스크립트 실행, 이벤트 처리 등)을 할 수 있음
    • promise.then보다 세련되게 promise의 result 값을 얻을 수 있는 문법
  • 주의사항
    • await는 최상위 레벨 스코프에서 작동하지 않는다 ⇒ 익명 async 함수로 코드를 감싸면 사용 가능

3 - Node.js 

3-1. Node.js 모듈 사용법

  • 모든 모듈은 '모듈을 사용하기 위해 불러오는 과정'이 필요
    • 브라우저에서 다른 파일을 불러올 때에는 <script> 태그를 이용
    • Node.js 에서는 JavaScript 코드 가장 상단에 require 구문을 이용하여 다른 파일을 불러옴
    const fs = require('fs'); // 파일 시스템 모듈을 불러옵니다
    const dns = require('dns'); // DNS 모듈을 불러옵니다
    
    // 이제 fs.readFile 메서드 등을 사용할 수 있습니다!
  • 3rd-party 모듈 사용하는 법
    • 프로그래밍 언어에서 공식적으로 제공하는 빌트인 모듈(built-in module)이 아닌 모든 외부 모듈
    • 서드 파티 모듈을 다운로드하기 위해서는 npm을 사용
    npm install underscore
    
    • node_modules에 underscore가 설치되면 내장 모듈을 사용하듯 require 구문을 통해 사용할 수 있음
    const _ = require('underscore');
    

3-2. Node.js 공식 문서 가이드

fs.readFile을 통해 알아보기 (fs.readFile의 공식 API 문서)

메서드 fs.readFile비동기적 으로 파일 내용 전체를 읽으며 전달인자를 3개 받음.

fs.readFile(path[, options], callback)
  • path \<string> | \<Buffer> | \<URL> | \<integer>
filename or file descriptor
    • path 자리에는 파일 이름을 받음. 네 가지 타입이 가능하지만 일반적으로 문자열(<string>)을 받음.
      //'etc/passwd' 라는 파일을 불러오는 경우
      fs.readFile('/etc/passwd', ..., ...)
  • options \<Object> | \<string>
If options is a string, then it specifies the encoding:
    • options는 선택적 전달인자로 문자열 또는 객체 형태. 문자열이면 인코딩 정보.
      //options에 객체를 전달한 경우
      let options = {
        encoding: 'utf8', // utf8 인코딩 방식으로 엽니다
        flag: 'r' // 읽기 위해 엽니다
      }
      
      // /etc/passwd 파일을 options를 사용하여 읽습니다.
      fs.readFile('/etc/passwd', options, ...)

      // options에 문자열을 전달한 경우 // /etc/passwd 파일을 'utf8'을 사용하여 읽습니다.
      fs.readFile('/etc/passwd', 'utf8', ...);

callback \<Function>

  • err \<Error> | \<AggregateError>
  • data \<string> | \<Buffer>
If no encoding is specified, then the raw buffer is returned.
  • 파일을 읽고 난 후에 비동기적으로 실행될 콜백함수를 전달.
  • 콜백 함수에는 두 가지 매개변수 err와 data가 존재. 에러가 발생하지 않으면 err 는 null 이 되며, data(파일의 내용)에 string 또는 Buffer 라는 객체가 전달됨.
  • option에서 인코딩이 특정되지 않으면 Buffer가 리턴됨.
  • 실습 : JavaScript 파일이 실행되는 디렉토리, 적당한 텍스트 파일(test.txt)을 새롭게 생성해 읽어오기.
fs.readFile('test.txt', 'utf8', (err, data) => {
  if (err) {
    throw err; // 에러를 던집니다.
  }
  console.log(data);
});
이것은 nodejs의 fs라는 모듈을 테스트하기 위해 만든 text.txt 파일의 내용이다.
js파일을 만든 뒤 터미널 창에서 node test.js를 입력하면
터미널창에 이 파일의 내용이 뜰 것이다.

과제. Part 2 - fs 모듈과 promise

  1. fs 모듈 활용하기
    • path 읽어오기 오류
      • ‘utf-8’ 인코딩을 안해서 데이터를 못 읽어옴
      • 파일 경로가 js 파일 기준이 아니라 실행되는 환경 기준으로 적혀야 함
    • promise로 표현하기
      • 함수에서 promise를 리턴하고 바로 fs.readFIle을 실행해, err가 있을 때 reject(err), 없을 때 resolve(data)를 전달
  2. promise 생성자
  3. 체이닝 기초
  • chaining 문제
// HINT: getDataFromFilePromise(user1Path) 및 getDataFromFilePromise(user2Path)를 이용해 작성합니다
const readAllUsersChaining = () => {
  // TODO: 여러개의 Promise를 then으로 연결하여 작성합니다
  return getDataFromFilePromise(user1Path)
    //1레벨의 1번째 then
    //파라미터로 data1을 받고, return 값은 (2레벨 then에서 리턴한) 배열
    .then((data1)=> {
      return getDataFromFilePromise(user2Path)
            .then((data2) => {
              return `[${data1}, ${data2}]`
            })
    })
    //2레벨의 2번째 then
    .then((result) => JSON.parse(result))
}
  1. Promise.all 로 리팩토링
  2. async / await 로 리팩토링