1. curl 명령어

 -터미널에서 localhost에 접근하여 get 또는 post 요청을 시험해볼 수 있음

 1) curl http://localhost:3000/login

  -curl 뒤에 입력한 localhost 주소의 html 문서를 호출

 

 

 2) curl http://localhost:3000/login -X POST -d '{"id":"user1","pw":"1234"}' -H "Content-Type: application/json"

  -'-X POST'는 post 요청을 하겠다는 의미

  -'-d'는 데이터를 의미하며 뒤에 json형태의 문자열을 넣어 해당 주소(http://localhost:3000/login)에 넘겨줄 데이터를 지정

  -'-H "Content-Type: application/json"'를 사용하여 헤더를 지정, 넘겨줄 데이터의 형식을 json이라고 명시해줌

  -login 경로에 미리 json파일로 저장해뒀던 아이디, 비밀번호 쌍 중 하나를 입력하여 post 요청을 하고, 그 응답으로 {"success": true}가 출력됨

  -다른 경우(아이디 오류, 비밀번호 오류)에 대해서도 각각의 요청에 대한 응답이 잘 출력됨

 

 3) curl http://localhost:3000/register -d '{"id":"user123","pw":"1234"}' -H "Content-Type: application/json"
  -회원가입 경로에 데이터를 전달하기

  -'-d'옵션을 통해 데이터를 전달해주는 부분이 있으면, 자동으로 post 메서드로 인식하기 때문에 '-X POST'는 없어도 됨

  -회원가입 페이지에 id와 pw 쌍을 입력하고 '-H "Content-Type: application/json"'을 이용해 넘겨줄 데이터가 json 형식임을 명시

  -데이터를 잘 넘겨받아 그에 대한 응답으로 {"success": true}를 출력했으며, 실제 회원가입 데이터를 저장하는 json 파일에도 새로운 데이터가 잘 저장됨

 

 

2. POSTMAN

 -API를 테스트해볼 수 있는 프로그램

 1) 다운로드 / 설치

  -구글에 postman을 입력 후 postman 다운로드

 

  -다운로드 후 설치 하고, 프로그램 실행

  -workspace > My Workspace

 

 2) API 테스트

  -new에서 HTTP Request

 

 

 2-1) GET 테스트

  -GET으로 지정한 곳 뒤에 localhost 주소를 입력한 뒤 send를 클릭하면 다음과 같이 해당 주소의 html문서를 볼 수 있음

 

 2-2) POST 테스트

  -GET을 POST로 바꾸면 POST 요청 실행 가능

  -body 탭에서 형식은 json으로 지정해주고 post할 데이터를 json 형태로 { "id": "user1", "pw": "1234" }와 같이 작성한 뒤 send

  -정상적으로 저장되어 있는 아이디와 비밀번호를 요청으로 보내주었으므로 { "success": true }를 응답으로 받을 수 있음

 

  -회원가입 경로에서도 새로운 로그인과 마찬가지로 id와 pw를 입력한 뒤 send 해주면 정상적으로 json 형식의 데이터를 받아 저장하고, { "success": true }로 응답해줌

 

  -또한, 위에서 입력한 id와 pw는 회원가입 정보를 받아 저장해두는 json파일인 users.json 파일에 추가로 저장되어 있음

 

 3) POSTMAN 폴더 관리

  -collection의 '+'를 눌러 새로운 폴더 생성

 

  -폴더의 이름 지정 가능

 

  -Add a request 버튼을 클릭하여 새로운 request 저장가능

 

  -request의 이름도 지정할 수 있으며, 그 외에는 위에서 한 것과 동일하게 설정하여, 반복적으로 테스트 해보고 싶은 API를 저장해두면 됨

 3) 부분 적용 함수

  -인자를 n개 받는 함수에 m개의 인자만 넘겨 기억시켰다가 나중에 나머지 (n-m)개의 인자를 넘기면 비로소 함수의 실행 결과를 얻을 수 있게끔 하는 함수

  -예시(bind 메서드)

var add = function () {
  var result = 0;
  for (var i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
  return result;
};
var addPartial = add.bind(null, 1, 2, 3, 4, 5);
console.log(addPartial(6, 7, 8, 9, 10));  //55

   →addPartial 함수는 인자 5개를 미리 적용(첫번째 인자 null은 this에 대한 것)

   →추후 나머지 인자들을 전달하면 모든 인자를 모아 원래의 함수가 실행

   →add 함수는 this를 사용하지 않아 bind 메서드만으로 구현되었지만 this 값을 변경할 수 밖에 없어, 메서드에 사용하려면 this에 관여하지 않도록 만들어야 함

 

  -예시(부분 적용 함수 구현 - 1)

var partial = function () {
  var originalPartialArgs = arguments;
  var func = originalPartialArgs[0];
  if (typeof func !== 'function') {
    throw new Error('첫 번째 인자가 함수가 아닙니다.');
  }
  return function () {
    var partialArgs = Array.prototype.slice.call(originalPartialArgs, 1);
    var restArgs = Array.prototype.slice.call(arguments);
    return func.apply(this, partialArgs.concat(restArgs));
  };
};

var add = function () {
  var result = 0;
  for (var i = 0; i <arguments.length; i++) {
    result += arguments[i];
  }
  return result;
};

var addPartial = partial(add, 1, 2, 3, 4, 5);
console.log(addPartial(6, 7, 8, 9, 10)); // 55

var dog = {
  name: '강아지',
  greet: partial(function(prefix, suffix) {
    return prefix + this.name + suffix;
  }, '왈왈, ')
};
dog.greet('입니다!');  // 왈왈, 강아지입니다!

   →addPartial에 partial 함수에 적용할 원본 함수와 미리 적용할 인자들을 전달

   →이후 addPartial에 나머지 인자들을 partial 함수가 반환하는 함수(부분 적용 함수)에 넘겨주어 이들을 한데 모아(concat) 원본 함수(add) 호출(apply)

   →실행 시점의 this를 그대로 반영하여 this에는 아무런 영향을 주지 않음

 

  -예시(부분 적용 함수 구현 - 2): 추후에 넘길 인자의 위치를 정하기

Object.defineProperty(window, '_', {
  value: 'EMPTY_SPACE',
  writable: false,
  configurable: false,
  enumerable: false
});

var partial2 = function () {
  var originalPartialArgs = arguments;
  var func = originalPartialArgs[0];
  if (typeof func !== 'function') {
    throw new Error('첫 번째 인자가 함수가 아닙니다.');
  }
  return function () {
    var partialArgs = Array.prototype.slice.call(originalPartialArgs, 1);
    var restArgs = Array.prototype.slice.call(arguments);
    for (var i = 0; i < partialArgs.length; i++) {
      if(partialArgs[i] === _) {
        partialArgs[i] = restArgs.shift();
      }
    }
    return func.apply(this, partialArgs.concat(restArgs));
  };
};

var add = function () {
  var result = 0;
  for (var i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
  return result;
};

var addPartial = partial2(add, 1, 2, _, 4, 5, _, _, 8, 9);
console.log(addPartial(3, 6, 7, 10)); // 55

var dog = {
  name: '강아지',
  greet: partial2(function(prefix, suffix) {
    return prefix + this.name + suffix;
  }, '왈왈, ')
};
dog.greet(' 배고파요!');  // 왈왈, 강아지 배고파요!

   →전역객체에서 Object.defineProperty를 이용해 '_'를 비워놓음의 의미로 준비(삭제, 변경 등 접근에 대한 방어 차원에서 여러 프로퍼티 설정 해둠)

   →또, partial2 함수 내에서 if 문을 통해 인자가 _인 부분을 골라내어 restArgs의 자리인 것을 명시적으로 나타냄

※ '_'문자를 '비워놓음'의 의미로 바꿀 때, Object.defineProperty 외에 Symbol.for을 사용해도 됨

(function () {
  var EmptySpace = Symbol.for('EMPTY_SPACE'); // 기존 전역 심볼공간에 'EMPTY_SPACE'라는 심볼을 새로 생성
  console.log(EmptySpace);
})();

(function () {
  console.log(Symbol.for(EmptySpace)); // 기존 전역 심볼공간에 'EMPTY_SPACE'라는 심볼이 있으므로 해당 값을 참조
})();

return function () {
  // ...
  for (var i = 0; i < partialArgs.length; i++) 
    if(partialArgs[i] === Symbol.for('EMPTH_SPACE')) { // 바뀐 부분
      partialArgs[i] = restArgs.shift();
    }
  }
  // ...
};

// ...

var _ = Symbol.for('EMPTY_SPACE'); // 추가된 부분, '_'의 심볼을 'EMPTY_SPACE'로 설정
var addPartial = partial(add, 1, 2, _, 4, 5, _, _, 8, 9);
console.log(addPartial(3, 6, 7, 10));

 

  -디바운스: 짧은 시간동안 동일한 이벤트가 많이 발생할 경우 모두 처리하지 않고 가장 처음 또는 마지막 이벤트에 대해 한 번만 처리

   →scroll, wheel, mousemove, resize 등에 적용

   →실무에서 부분 적용 함수 사용에 적합

  -예시(디바운스)

var debounce = function (eventName, func, wait) {
  var timeoutId = null;
  return function (event) {
    var self = this;
    console.log(eventName, 'event 발생');
    clearTimeout(timeoutId);
    timeoutId = setTimeout(func.bind(self, event), wait);
  };
};

var moveHandler = function (e) {
  console.log('move event 처리');
};
var wheelHandler = function (e) {
  console.log('wheel event 처리');
};
document.body.addEventListener('mousemove', debounce('move', moveHandler, 500));
document.body.addEventListener('mousewheel', debounce('wheel', wheelHandler, 700));

   →디바운스 함수는 어떤 이벤트가 발생했는지 출력하기 위한 eventName, 이벤트에 따라 실행할 함수인 func, 마지막으로 발생한 이벤트인지 판단하기 위한 대기시간인 wait(ms)를 인자로 받음

   →timeoutID의 초기값은 null로 주며 선언

   →EventListener에 의해 호출될 함수를 return

   →self에 this 객체를 미리 저장해둠

   →함수 내부에서는 어떤 이벤트가 발생했는지 eventName을 출력

   →clearTimeout()을 사용하여 대기큐를 무조건 초기화해주는데, 처음 이벤트 발생 후 함수를 실행시킬 대기 시간 이전에 반복해서 이벤크가 발생한다면, 여기서 대기큐를 초기화시켜 다시 대기 시간을 계산할 수 있게 해줌

   →마지막으로 setTimeout()을 사용하여 대기시간(wait)만큼 지연시킨 후 func 호출, 대기시간 이전에 동일한 이벤트 발생 시 반환할 함수가 다시 실행되며 윗 줄에서 clearTimeout되고 다시 대기시간 계산

   →디바운스 함수에서 클로저로 처리되는 변수는 eventName, func, wait, timeoutId

 

 

 4) 커링 함수

  -커링 함수: 여러 인자를 받는 함수를 하나의 인자를 받는 함수로 나눠 순차적으로 호출될 수 있도록 체인 형태로 구성한 것

  -예시

var curry3 = function (func) {
  return function (a) {
    return function (b) {
      return func(a, b);
    };
  };
};

var getMaxWith10 = curry3(Math.max)(10);
console.log(getMaxWith10(8));      // 10
console.log(getMaxWith10(25));    // 25

var getMaxWith10 = curry3(Math.min)(10);
console.log(getMaxWith10(8));      // 8
console.log(getMaxWith10(25));    // 10

   →인자 하나를 더 받아 10과 비교하여 더 큰 값을 찾는 함수

   →curry3에 인자로 Math.max함수를 주고, curry3 함수 내의 첫번째 함수의 인자로 10을 주어 저장

   →이후, 10과 비교할 값을 두번째 함수의 인자로 줌과 동시에 원본 함수(Math.max) 실행

   →마지막에 반환되는 원본 함수에서 curry3 함수 내의 다른 함수 두 개에서 a와 b 변수를 참조하여 사용하므로 변수 a, b는 클로저로 처리됨

 

  -커링 함수에서 마지막 인자가 전달되기 전까지 원본 함수는 실행되지 않음

  -커링 함수의 단점으로 가독성이 떨어짐

var curry5 = function (func) {
  return function (a) {
    return function (b) {
      return function (c) {
        return function (d) {
          return function (e) {
            return func(a, b, c, d, e);
          };
        };
      };
    };
  };
};

var getMax = curry5(Math.max);
console.log(getMax(1)(2)(3)(4)(5));

   →다행히 화살표 함수로 같은 내용을 한 줄에 표시 가능

var curry5 = func => a => b => c => d => e => func(a, b, c, d, e);

 

  -커링 함수는 당장 필요한 정보만 받아서 전달하고, 또 필요한 정보가 들어오면 전달하는 식으로 원하는 시점까지 지연시켰다가 실행하고 싶을 때 유용

var getInformation = baseUrl => path => id => fetch(baseUrl + path + '/' + id);

   →HTML5의 fetch 함수는 url을 받아 해당 url에 HTTP 요청을 함

   →이때 baseUrl은 몇 개로 고정되지만 path와 id는 많은 값을 가지므로 baseUrl만 기억시킨 후 특정 path와 id만으로 서버 요청을 수행하도록 하면 효육적이고 가독성 높아짐

 

  -url 요청에서 커링 함수 활용 예시

var imageUrl = 'http://imageAddress.com/';
var productUrl = 'http://productAddress.com/';

// 이미지 path 설정
var getImage = getInformation(imageUrl);         // http://imageAddress.com/
var getEmoticon = getImage('emoticon');          // http://imageAddress.com/emoticon
var getIcon = getImage('icon');                        // http://imageAddress.com/icon

// 제품 path 설정
var getProduct = getInformation(productUrl);   // http://productAddress.com/
var getFruit = getProduct('fruit');                     // http://productAddress.com/fruit
var getVegetable = getProduct('vegetable');     // http://productAddress.com/vegetable

// id까지 설정하여 실제 url 불러오기
var emoticon1 = getEmoticon(100);                // http://imageAddress.com/emoticon/100
var emoticon2 = getEmoticon(102);                // http://imageAddress.com/emoticon/102
var icon1 = getIcon(205);                              // http://imageAddress.com/icon/205
var icon2 = getIcon(234);                              // http://imageAddress.com/icon/234
var fruit1 = getFruit(300);                             // http://productAddress.com/fruit/300
var fruit2 = getFruit(400);                             // http://productAddress.com/fruit/400
var vegetable1 = getVegetable(456);             // http://productAddress.com/vegetable/456
var vegetable2 = getVegetable(789);             // http://productAddress.com/vegetable789

3. 클로저 활용 사례

 1) 콜백 함수 내부에서 외부 데이터 사용

  -예시 1)

var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');

fruits.forEach(function (fruit) {
  var $li = document.createElement('li');
  $li.innerText = fruit;
  $li.addEventListener('click', function () {
    alert('your choice is' + fruit);
  });
  $ul.appendChild($li);
});
document.body.appendChild($ul);

  -ul 생성, ul 내부에 fruits 목록에 있는 과일을 하나씩 li로 추가

   →li에 fruits 목록의 값을 하나씩 추가하는 forEach 함수는 외부 변수를 사용하지 않으므로 클로저가 없음

  -li를 클릭하면 "'your choice is' + fruit" 출력, 이때, fruit는 addEventListener의 콜백 함수 function 내부에서 외부의 변수를 참조하여 사용

  →따라서, addEventListener의 콜백 함수에는 클로저가 있음

 

  -예시 2)

var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');

var alertFruit = function(fruit) {
      alert('your choice is' + fruit);
};
fruits.forEach(function(fruit) {
    var $li = document.createElement('li');
    $li.innerText = fruit;
    $li.addEventListener('click', alertFruit);
    $ul.appendChild($li);
});
document.body.appendChild($ul);
alertFruit(fruits[1]);

  -공통적으로 사용되는 함수인 alertFruit를 콜백 함수 외부로 꺼냄

  -마지막 코드인 alertFruit(fruits[1])에서 fruits[1]은 정상적으로 'banana'를 불러와 수행

  -하지만 클릭 시 수행되는 alertFruit는 대상 과일명이 아닌 [object PointerEvent]가 출력됨

   →콜백 함수의 인자에 대한 제어권이 addEventListener한테 있는 상태

   →addEventListener는 콜백 함수 호출 시 첫번째 인자에 이벤트 객체를 넣기 때문에 alertFruit에 이벤트 객체 [Object PointerEvent]가 들어감

   →bind 메서드로 해결 가능

var fruits = ['apple', 'banana', 'peach'];
var $ul = document.createElement('ul');

var alertFruit = function(fruit) {
      alert('your choice is' + fruit);
};
fruits.forEach(function(fruit) {
    var $li = document.createElement('li');
    $li.innerText = fruit;
    $li.addEventListener('click', alertFruit.bind(null,fruit));
    $ul.appendChild($li);
});
document.body.appendChild($ul);
alertFruit(fruits[1]);

   →bind 메서드를 사용하면 첫번째 인자는 바인딩할 this인데 이 값은 생략할 수 없으므로 null로 정의해야하고, 이 때문에 원래의 this를 유지할 수 없는 경우가 많음

   →두번째 인자에는 이벤트 객체가 넘어옴

...

var alertFruit = function(fruit, s) {
      alert('your choice is' + fruit + ' ' + s);
};

...

   →다음과 같이 임의의 변수 s를 지정해주면 bind의 인자로 null로 정의한 this와 fruit 외에 암시적으로 이벤트 객체를 넘겨줬음을 알 수 있음

   →해결을 위해 고차함수를 활용하면 됨

...

var alertFruitBuilder = function (fruit) {
  return function () {
    alert('your choice is ' + fruit);
  };
};
fruits.forEach(function (fruit) {
  var $li = document.createElement('li');
  $li.innerText = fruit;
  $li.addEventListener('click', alertFruitBuilder(fruit));
  $ul.appendChild($li);
});

...

   →alertFruitBuilder로 정의한 함수에서 익명 함수를 반환하는데 이 익명함수가 기존의 alertFruit 함수임

   →addEventListener의 콜백 함수로 alertFruitBuilder를 지정하면 alertFruitBuilder는 fruit값을 받아 alert를 띄우는 함수를 반환함

   →결국, fruit를 받아 alert를 띄우는 함수가 addEventListener의 콜백 함수가 되고, 클릭이 발생했을 때, alert('your choice is ' + fruit)가 바로 fruit를 참조함

   →alertFruitBuilder가 반환하는 함수에는 클로저가 존재함

 

 

 2) 접근 권한 제어(정보 은닉)

  -정보 은닉: 어떤 모듈의 내부 로직에 대해 외부로의 노출을 최소화

   →모듈 간의 결합도 낮추고 유연성 높임

  -접근 권한의 종류

   →public: 외부에서 접근이 가능

   →private: 내부에서만 사용하며 외부에 노출되지 않는 것

   →protected: private이지만 동일 패키지의 클래스나 하위 클래스 관계에서 접근 가능, 거의 사용 안함

  -함수 내에서 return을 사용하여 선택적으로 일부 변수에만 접근 권한을 부여할 수 있음

var outer = function () {
  var a = 1;
  var inner = function () {
    return ++a;
  };
  return inner;
};
var outer2 = outer();
console.log(outer2());
console.log(outer2());

   →outer라는 변수를 통해 outer 함수를 실행할 수는 있지만, outer 함수 내부에는 개입 불가

   →외부에서는 오직 outer함수가 return한 정보(inner)에만 접근 가능

   →return한 변수들은 공개 멤버, return하지 않은 정보는 비공개 멤버

 

  -예시(자동차 경주 게임)

   →규칙 1) 각 턴마다 주사위를 굴려 나온 숫자(km)만큼 이동

   →규칙 2) 차량별 연료(fuel), 연비(power)는 무작위

   →규칙 3) 남은 연료가 이동할 거리에 필요한 연료보다 부족하면 이동 못함

   →규칙 4) 모든 유저가 이동할 수 없는 턴에 게임 종료

   →규칙 5) 게임 종료 시점에 가장 멀리 이동해 있는 사람이 승리

// 규칙에 따라 생성한 자동차 객체

var car = {
  fuel: Math.ceil(Math.random() * 10 + 10),
  power: Math.ceil(Math.random() * 3 + 2),
  moved: 0,
  run: function () {
    var km = Math.ceil(Math.random() * 6);
    var wasteFuel = km / this.power;
    if (this.fuel < wasteFuel) {
      console.log('이동불가');
      return;
    }
    this.fuel -= wasteFuel;
    this.moved += km;
    console.log(km + 'km 이동 (총 ' + this.moved + 'km)');
  }
};

   →랜덤으로 설정되는 fuel, power와 이동거리인 moved에 접근이 가능하여 다음과 같이 사용자가 조작 가능

car.fuel = 10000;
car.power = 100;
car.moved = 1000;

   →문제 해결을 위해 자동차를 객체가 아닌 함수로 만들고, 필요한 멤버만 return하면 됨

// 규칙에 따라 생성한 자동차 함수

var createCar = function () {
  var fuel = Math.ceil(Math.random() * 10 + 10);
  var power = Math.ceil(Math.random() * 3 + 2);
  var moved = 0;
  return {
    get moved () {
      return moved;
    },
    run = function () {
      var km = Math.ceil(Math.random() * 6);
      var wasteFuel = km / power;
      if (fuel < wasteFuel) {
        console.log('이동불가');
        return;
      }
      fuel -= wasteFuel;
      moved += km;
      console.log(km + 'km 이동 (총 ' + moved + 'km). 남은 연료: ' + fuel);
    }
  };
};
var car = createCar();

   →fuel과 power는 비공개 멤버로 함수 외부에서 접근할 수 없고, moved는 getter만 부여하여 읽기 전용

   →외부에서는 moved를 읽는 것과 run 메서드를 실행하는 것만 가능

console.log(car.moved) // 지금까지 총 이동거리 출력
consoel.log(car.fuel) // undefined
consoel.log(car.power) // undefined

car.fuel = 1000;          // car.fuel에 1000을 대입할 수 있고
console.log(car.fuel);  // 출력하면 대입한 값인 1000이 나오지만
car.run();                  // run함수에까지 영향을 미치지는 못함, power와 moved도 마찬가지

   →여전히 run 메서드를 다른 내용으로 덮어씌우는 어뷰징은 가능한 상태이고 이것까지 막으려면 freeze 등의 메서드 사용

1. 클로저의 의미 및 원리

 -A closure is the combination of a function and the lexical environment within that function was declared. -MDN-

 -클로저는 함수와 그 함수가 선언될 당시의 lexical environment의 상호관계에 따른 현상

 -여기서 함수가 선언될 당시의 lexical environment는 lexical environment의 outerEnvironmentReference에 해당

 

[코어 자바스크립트] 02. 실행 컨텍스트

1. 실행 컨텍스트 1) 실행 컨텍스트: 실행할 코드에 제공할 환경 정보들을 모아놓은 객체 -선언된 변수, 함수 등을 실행 컨텍스트에 저장해두었다가 호출하면 나옴 2) 스택(stack): 우물 같은 구조로

data-science-study.tistory.com

 -윗글을 참조하면  outerEnvironmentReference는 현재 컨텍스트 바깥의 컨텍스트에 접근할 수 있도록 바깥의 컨텍스트를 저장

     ex) 컨텍스트 A → 내부에 존재하는 내부함수 B

          ▶내부함수 B의 실행 컨텍스트가 활성화된 시점에서 B의 outerEnvironmentReference가 A의 lexical environment를 참조하므로 A의 변수에 접근 가능

          ▶A는 B에서 선언한 변수에 접근 불가능

 -예시에서 B가 A의 변수에 접근가능하여 사용하면 B와 A가 상호작용을 한 것이지만 A의 변수를 무조건 쓰지는 않으므로 상호작용 하지 않을 수도 있음

 -즉, 내부함수에서 외부 변수를 참조하는 경우에 한해서만 combination이 발생

 

 -결론적으로, 어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상

     (컨텍스트 A에서 선언한 변수를 내부함수 B에서 참조하는 경우에 클로저 발생)

 

 -예시 1)

var outer = function() {
  var a = 1;
  var inner = function() {
    console.log(++a);
  };
  inner();
};
outer();

  →outer 함수에서 변수 a를 선언

  →outer 함수 내에서 inner 함수 선언

  →inner 함수 내에서 a의 값을 1 증가시킨 후 출력

  →inner 함수 내에 변수 a가 선언되지 않았으므로 environmentRecord에서 값을 찾지 못하고 outerenvironmentReference에 지정된 outer 함수의 environmentRecord에 접근하여 outer 함수에서 지정된 변수 a에 접근

  →따라서, '2' 출력

 

 -예시 2)

var outer = function() {
  var a = 1;
  var inner = function() {
    return ++a;
  };
  return inner();
};
var outer2 = outer();
console.log(outer2);

  →이번에는 outer 함수 내에서 inner() 함수의 실행 결과(2)를 반환

  →outer 함수의 실행 컨텍스트가 종료된 시점 이후 a 변수와 inner 함수는 사라질 것

  →outer2에는 inner 함수의 실행결과인 '2'가 저장되어, '2'가 출력되며 outer2를 이후에 반복해서 출력해도 '2'만 출력

 

 -예시 3)

var outer = function() {
  var a = 1;
  var inner = function() {
    return ++a;
  };
  return inner;
};
var outer2 = outer();
console.log(outer2());
console.log(outer2());

  →inner 함수 자체를 반환하여 outer2에는 inner(a에 1씩 더한 값을 반환하는) 함수가 저장됨

  →예시 2)에서 outer2 = 2였다면 예시 3)에서 outer2 = function() { return ++a }

  →따라서, outer2()를 실행할 때마다 ++a, 즉 a에 1씩 더해져 반환되고 반복해서 실행하면 계속 1씩 더해진 값을 가질 수 있음

  →outer 함수의 실행 종료 시점에서 inner 함수를 반환하고 inner 함수를 반환하고 나면 outer 함수는 종료된 상태

  →이 때, inner 함수는 outer2에 저장되어 outer2를 실행하면 언제든 호출될 가능성이 열린 것이므로 inner 함수가 참조해야하는 outer 함수의 LexicalEnvironment는 가비지 콜렉터의 수집대상에서 제외됨

 

 -예제 3)을 통해 알 수 있는 클로저의 정의

  →어떤 함수 A(outer)에서 선언한 변수(a)를 참조하는 내부함수 B(inner)를 외부로 전달(outer2에 inner 함수 자체를 반환)할 경우, A의 실행 컨텍스트 종료 이후에도 변수가 사라지지 않는 현상

  →이때, 외부로 전달이 return만 의미하는 것은 아님

 

 -예시 4)

( function () {
    var a = 0;
    var intervalId = null;
    var inner = function () {
      if (++a >=10 ) {
        clearInterval(intervalId);
      }
      console.log(a);
    };
    intervalId = setInterval(inner, 1000);
})();

  →setInterval에 전달할 콜백 함수 inner는 inner 외부에서 선언된 변수 a를 참조

 

 -예시 5)

( function () {
    var count = 0;
    var button = document.createElement('button');
    button.innerText = 'click';
    button.addEventListener('click', function () {
      console.log(++count, 'times clicked');
    });
    document.body.appendChild(button);
})();

  →'click'이라고 쓰인 버튼을 생성하고, 버튼을 클릭할 때마다 count가 1씩 더해진 값과 'times clicked' 문자가 함께 출력

  →이때, addEventListener에서 실행할 함수 내부에서 외부에 있는 변수 count를 참조함

 

 -예제 5), 6) 모두 지역변수(a, count)를 참조하는 내부함수(inner, function)를 외부(setInterval, button)으로 전달했기 때문에 클로저

 

 

2. 클로저와 메모리 관리

 -클로저는 어떤 필요에 의해 의도적으로 함수의 지역변수가 메모리를 소모하도록(a가 사라지지 않고 계속 참조되도록 하는 등) 함으로써 발생

 -필요성이 사라진 시점에서 메모리를 더 소모하지 않도록 해주면 됨

 -참조 카운트를 0으로 만들어 더 이상 참조되지도 않고 쓰이지도 않게 된다면 메모리가 회수될 것

 -이 때, 참조 카운트를 0으로 만드는 방법으로 null이나 undefined를 할당하면 됨

 

 -예시 1)

var outer = (function () {
  var a = 1;
  var inner = function () {
    return ++a;
  };
  return inner;
})();
console.log(outer());
console.log(outer());
outer = null; // outer 식별자의 inner 함수 참조를 끊음

  →outer 함수를 null로 정의하며 outer 함수 내부에서 변수 a를 참조하던 inner 함수를 없애며 a를 참조하는 컨텍스트가 아무것도 없도록 만듦

 -예시 2)

( function () {
    var a = 0;
    var intervalId = null;
    var inner = function () {
      if (++a >=10) {
        clearInterval(intervalId);
        inner = null;  // inner 식별자의 함수 참조를 끊음
      }
      console.log(a);
    };
    intervalId = setInterval(inner, 1000);
})();

  →함수 내부에서 변수를 참조하던 내부함수 inner 자체를 null로 정의하면 함수 내부변수 a룰 참조하는 것이 없도록 만듦

 

 -예시 3)

( function () {
    var count = 0;
    var button = dovument.createElement('button');
    button.innerText = 'click';
    
    var clickHandler = function () {
      concole.log(++count, 'times clicked');
      if (count >= 10) {
        button.removeEventListener('click', clickHandler);
        clickHandler = null;  // clickHandler 식별자의 함수 참주 끊음
      }
    };
    button.addEventListener('click', clickHandler);
    document.body.appendChild(button);
})();

  →count 변수를 계속 참조하며 button에 eventListener로 주는 함수 clickHandler를 null로 정의하며 count를 참조할 컨텍스트를 없앰

5. 콜백 지옥과 비동기 제어

 1) 콜백 지옥: 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 너무 깊어지는 현상

  -비동기적 작업에서 콜백 지옥이 자주 등장

 

 2) 비동기: 동기의 반대말로, 현재 코드 실행 완료 여부와 무관하게 다음 코드 실행

  -동기적인 코드는 현재 실행 중인 코드가 완료된 후 다음 코드 실행

  -CPU 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적인 코드

  -setTimeout, addEventListener, XMLHttpRequest 등 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 비동기적 코드

  -웹의 복잡도에 따라 비동기적 코드의 비중이 높아졌고 콜백 지옥에 빠지기 쉬워짐

 

 3) 콜백 지옥 예시

setTimeout(function (name) {
  var coffeeList = name;
  console.log(coffeeList);
  
  setTimeout(function (name) {
    coffeeList += ', ' + name;
    console.log(coffeeList);
    
    setTimeout(function (name) {
      coffeeList += ', ' + name;
      console.log(coffeeList);
      
      setTimeout(function (name) {
        coffeeList += ', ' + name;
        console.log(coffeeList);
      }, 500, '카페라떼');
    }, 500, '카페모카');
  }, 500, '아메리카노');
}, 500, '에스프레소');

// 결과
// 에스프레소
// 에스프레소, 아메리카노
// 에스프레소, 아메리카노, 카페모카
// 에스프레소, 아메리카노, 카페모카, 카페라떼

  -0.5초마다 커피 이름을 하나씩 추가하여 출력

  -들여쓰기가 깊고, 값이 전달되는 순서가 아래에서 위로 어색하게 향함

 

 4) 해결방법

  (1) 기명함수로 변환

var coffeeList = '';

var addEspresso = function (name) {
  coffeeList = name;
  console.log(coffeeList);
  setTimeout(addAmericano, 500, '아메리카노');
};
var addAmericano = function (name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
  setTimeout(addMocha, 500, '카페모카');
};
var addMocha = function (name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
  setTimeout(addLatte, 500, '카페라떼');
};
var addLatte = function (name) {
  coffeeList += ', ' + name;
  console.log(coffeeList);
};

setTimeout(addEspresso, 500, '에스프레소');

  -가독성이 높아지고, 위에서 아래로 읽어내려가는대로 출력되어 어색함도 사라짐

  -일회성 함수를 매번 변수에 할당하는 것이 복잡해 보일 수 있음

 

  (2) promise

   -방법 1

new Promise(function (resolve) {
  setTimeout(function () {
    var name = '에스프레소';
    console.log(name);
    resolve(name);  // promise가 fufilled(성공)일 때, name 반환
  }, 500);
}).then(function (prevName) {  // promise.then에서 위의 resolve 결과를 받아 인자로 사용
  return new Promise(function (resolve) {
    setTimeout(function () {
      var name = prevName + ', 아메리카노';  // 인자로 받은 초기 name에 '아메리카노' 추가
      console.log(name);
      resolve(name);  // 아메리카노가 추가된 name을 다시 반환
    }, 500);
  });
}).then(function (prevName) {  // 위에서 resolve로 넘겨준 아메리카노가 추가된 name을 인자로 사용
  return new Promise(function (resolve) {  // 이후 반복
    setTimeout(function () {
      var name = prevName + ', 카페모카';
      console.log(name);
      resolve(name);
    }, 500);
  });
}).then(function (prevName) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      var name = prevName + ', 카페라떼';
      console.log(name);
      resolve(name);
    }, 500);
  });
});

   -resolve 또는 reject가 있으면 둘 중 하나가 실행되기 전까지 다음 then 또는 catch로 넘어가지 않으므로 비동기 작업의 동기적 표현이 가능

 

   -방법 2

var addCoffee = function (name) {
  return function (prevName) {
    return new Promise(function (resolve) {
      setTimeout(function () {
        var newName = prevName ? (prevName + ', ' + name) : name;
        console.log(newName);
        resolve(newName);
      }, 500);
    });
  };
};

addCoffee('에스프레소')()
  .then(addCoffee('아메리카노'))
  .then(addCoffee('카페모카'))
  .then(addCoffee('카페라떼'));

 

   -방법 1에서 반복적이던 내용을 함수화해서 짧게 표현

 

  (3) Generator

var addCoffee = function(prevName, name) {
  setTimeout(function () {
    coffeeMaker.next(prevName ? prevName + ', ' +name : name);
  }, 500);
};
var coffeeGenerator = function* () {  // Generator 함수
  var espresso = yield addCoffee('', '에스프레소');
  console.log(espresso);
  var americano = yield addCoffee(espresso, '아메리카노');
  console.log(americano);
  var mocha = yield addCoffee(americano, '카페모카');
  console.log(mocha);
  var latte = yield addCoffee(mocha, '카페라떼');
  console.log(latte);
};
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();

   -Generator 함수를 실행하면 Iterator가 반환되며

   -Iterator는 next라는 메서드를 가짐

   -next를 실행하면 Generator 함수 내 가장 첫번째 yield에서 함수의 실행을 멈춤

   -이후 다시 next 메서드 호출하면 멈췄던 부분부터 시작해서 다음 yield에서 함수의 실행을 멈춤

   -비동기 작업이 완료되는 시점(console에 name 출력)마다 next를 호출하면 Generator 함수가 위에서 아래로 순차적으로 진행됨

 

  (4) promise + async/await

var addCoffee = function (name) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(name);
    }, 500);
  });
};

var coffeeMaker = async function () {
  var coffeeList = '';
  var _addCoffee = async function (name) {
    coffeeList += (coffeeList ? ',' : '') + await addCoffee(name);
  };
  await _addCoffee('에스프레소');
  console.log(coffeeList);
  await _addCoffee('아메리카노');
  console.log(coffeeList);
  await _addCoffee('카페모카');
  console.log(coffeeList);
  await _addCoffee('카페라떼');
  console.log(coffeeList);
};
coffeeMaker();

   -비동기 작업을 수행하고자 하는 함수 앞에 async 표기

   -함수 내부에 실질적으로 비동기 작업이 필요한 위치마다 await 표기

   -await를 쓰려면 함수 앞에 반드시 async가 표기되어 있어야 함

1. 콜백 함수: 다른 코드의 인자로 넘겨주는 함수

  -콜백 함수를 넘겨받은 코드는 콜백 함수를 필요에 따라 적절한 시점에 실행할 것

  -특정 코드에 콜백 함수에 대한 제어권을 넘겨주는 것

 

 

2. 제어권

 1) 호출 시점에 대한 제어권

  -예시

var count = 0;
var timer = setInterval(function () {
    console.log(count);
    if(++count>4) clearInterval(timer);
},300);

// 결과
// 0 (0.3초)
// 1 (0.6초)
// 2 (0.9초)
// 3 (1.2초)
// 4 (1.5초)

 -setInterval 함수의 기본 인자는 함수와 delay로, delay 값마다 함수를 실행

  -clearInterval은 setInterval함수를 즉시 종료하도 싶을 때 사용, 인자는 setInterval함수가 리턴해주는 값으로 사용(예시에서는 timer)

  -즉, 예시는 300ms마다 console에 count를 출력하고,

           1을 더한 후 4와 비교,

           4보다 크지 않으면 반복실행,

           4보다 크면 즉시 종료

 

  -설명을 위한 예시 정리

var count = 0;
var cbFunc = function() {
  console.log(count);
  if(++count>4) clearInterval(timer);
};
var timer = setInterval(cbFunc, 300);

  -cbFunc에 대한 제어권은 원래 사용자에게 있음

   →cbFunc를 정의한 뒤 사용하려면, 코드를 "cbFunc() "와 같이 입력하여 직접 호출해야 함

   →이렇게 하면 count에 1씩 더해진 값이 정해져 실행 횟수에 따라 count가 1씩 증가하여 출력되는 것은 똑같지만,

     4와 비교되는 것이 출력 이후라 4보다 크다고 출력이 안되는 것은 아님

   →또한, 매번 코드를 입력하여 호출해야함

 

  -setInterval 함수의 인자로 cbFunc()함수를 넘겨주며 cbFunc()함수의 제어권을 setInterval 함수로 넘김

   →setInterval 함수가 스스로 판단하여, 두번째 인자로 받은 숫자만큼의 시간마다 자동으로 cbFunc()함수를 실행

  →매번 count를 4와 비교하는 동작도 실행하다가 4보다 커지면 setInterval 함수 자체를 중지하며 4 이후의 숫자의 출력은 중지됨

 

  -콜백 함수의 제어권을 넘겨받은 코드(setInterval)는 콜백 함수 호출 시점(cbFunc 실행, count를 console에 출력)에 대한 제어권을 가짐

 

 

 2) 인자에 대한 제어권

  -예시

var newArr = [10, 20, 30].map(function (currentValue, index) {
  console.log(currentValue, index);
  return currentValue + 5;
});
console.log(newArr);

// 결과
// 10 0
// 20 1
// 30 2
// [15, 25, 35]

  -map메서드는 메서드 대상이 되는 배열(예시에서 [10, 20 ,30])의 모든 요소들을 처음부터 끝까지 하나씩 콜백 함수에 적용,

   콜백 함수의 실행 결과를 모아 새로운 배열 생성

  -즉, 예시에서 10, 20, 30 각각의 값과 인덱스를 하나씩 출력하고,

   새로운 배열에 저장하기 전 값에 5씩 더하여 새로운 배열([15, 25, 35]) 생성하여 출력

 

  -예시 2

var newArr = [10, 20, 30].map(function (index, currentValue) {
  console.log(index, currentValue);
  return currentValue + 5;
});
console.log(newArr);

// 결과
// 10 0
// 20 1
// 30 2
// [5, 6, 7]

  -콜백 함수의 인자 index와 currentValue의 위치를 바꿈

   →map 메서드의 콜백 함수는 기본적으로 (요소, 인덱스, [, 배열], [, thisArg])의 순서로 인자를 받도록 되어 있음

  →즉, map 메서드 내의 콜백 함수에서 (index, currentValue)로 (인덱스, 요소)의 순서로 이름을 붙였다고 해도 이름만 붙인 것일 뿐

       실제로, 컴퓨터에서는 index라고 이름 붙은 요소, currentValue라고 이름 붙은 인덱스라고 인식함

  →따라서, index, currentValue 순서로 출력을 명령하면 (index가 아닌 요소, currentValue가 아닌 인덱스)의 순서로 출력됨

  →또한, +5가 더해지는 주체도 currnetValue의 이름이 붙은 인덱스이므로 0, 1, 2에 각각 5씩 더해져 새로운 배열에 저장됨

 

  -콜백 함수의 제어권을 넘겨받은 코드(map)은 콜백 함수 인자의 순서(요소, 인덱스)에 대한 제어권을 가짐

 

 

 3) this에 대한 제어권

  -콜백 함수도 함수이기 때문에 별도로 this를 지정하지 않으면 this에는 기본적으로 전역객체가 설정됨

  -예시

Array.prototype.map = function(callback, thisArg) {
  var mappedArr = [];
  for (var i = 0; i < this.length ; i++) {
    var mappedValue = callback.call(thisArg || window, this[i], i, this);
    mappedArr[i] = mappedValue;
  }
  return mappedArr;
};

  -callback 함수의 제어권을 넘겨받는 코드(function())에서 this 객체를 thisArg에서 지정해준다면, 콜백 함수를 call할 때, 콜백 함수의 this를 function()에서 넘겨준 thisArg를 지정하고, 넘겨주지 않으면 기본적으로 설정된대로 전역객체를 지정함

 

 

3. 콜백 함수는 함수다

  -콜백 함수로 어떤 객체의 메서드를 전달하더라도 그 메서드는 메서드가 아닌 함수로 호출됨

  -예시

var obj = {
  vals: [1, 2, 3],
  logValues: function(v, i) {
    console.log(this, v, i);
  }
};
obj.logValues(1, 2);
[4, 5, 6].forEach(obj.logValues);

  -obj.logValues(1, 2): obj 객체의 logValues 메서드로서 정의되어 this는 obj 객체를 가리키고 1과 2는 각각 function의 인자 v와 i로 할당되어 출력됨

  -[4, 5, 6].forEach(obj.logValues): obj,logValues를 forEach의 콜백 함수로 사용하여, obj 객체의 logValues 메서드가 아닌 obj.logValues가 담고 있는 함수 function만 전달됨

   →obj 객체와의 직접적인 연관이 없어지고 forEach에서 별도의 this 객체를 지정하지 않아 this는 전역객체를 가리키게 됨

   →객체의 메서드를 전달하더라도 콜백 함수로서 전달하면 메서드가 아닌 함수로 전달됨

 

 

4. 콜백 함수 내부의 this에 다른 값 바인딩

  -위에서 봤듯이, 원래 객체의 메서드를 콜백 함수로 전달하면 해당 객체를 this로 설정할 수 없음

  -이때, 콜백 함수 내부에서 메서드의 객체를 this로 설정하는 방법

  -전통적인 방법: 메서드 내에서 임의의 변수(self 등)에 this를 담아 메서드를 담은 객체를 this로 설정

var obj1 = {
    name: 'obj1',
    func: function() {
        var self = this;
        return function() {
            console.log(self.name);
        };
    }
};
var callback = obj1.func();
setTimeout(callback, 1000);

  -setTimeout 함수의 콜백 함수로 obj1 객체의 func 메서드를 주어 함수로 변경되어 this가 전역 객체를 가리켜야 하지만, 메서드 내에서 self에 this를 할당했으므로 self.name은 obj1 객체의 name을 가리키게 됨

  -따라서, 1000ms(1초) 뒤에 'obj1'이 출력됨

// self라는 임의의 변수 생성없이 하드코딩하는 방법도 있지만, this를 이용한 재활용을 할 수 없게 됨
var obj1 = {
    name: 'obj1',
    func: function() {
            console.log(obj1.name);
    }
};
setTimeout(obj1.func, 1000);

 

  -var self = this로 지정한 것을 재활용하는 예시

var obj1 = {
    name: 'obj1',
    func: function() {
        var self = this;
        return function() {
            console.log(self.name);
        };
    }
};

// obj1의 func를 복사한 것을 obj2내의 func로 사용
var obj2 = {
  name: 'obj2',
  func: obj1.func
};
var callback2 = obj2.func();
setTimeout(callback2, 1500);

// obj3은 name만 지정한 후, call을 사용하여 obj1의 func를 obj3에 적용
var obj3 = { name: 'obj3' };
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000);

   →obj1 객체 내에서 func 메서드의 this를 self로 지정해놨으므로 obj2, obj3에 복사되어 이용될 때, self에는 obj1의 func를 호출할 때의 각 객체, obj2와 obj3이 각각 할당됨

   →따라서, self.name에는 obj2.name, obj3.name이 설정되어 1.5초, 2초 뒤에 'obj2', 'obj3'이 각각 출력됨

 

  -this.name을 obj1.name으로 설정하면 obj1 객체의 func를 콜백 함수로 사용할 때는 obj1 객체를 콜백함수의 this로 지정할 수 있지만, obj2, obj3 등에서 func를 콜백함수로써 재활용하지 못함

  -하지만, this를 self 등의 변수에 넣어두면, 메서드를 호출하는 객체가 바뀔 때마다 메서드의 this를 해당 객체로 바꿔서 지정해줄 수 있으므로 재활용이 가능해짐

 

  -전통적인 방법 외에 bind를 사용하는 방법

var obj1 = {
  name: 'obj1',
  func: function() {
    console.log(this.name);
  }
};
setTimeout(obj1.func.bind(obj1), 1000);

// obj1 객체의 func 메서드를 obj2에 bind하여 사용
var obj2 = { name: 'obj2'};
setTimeout(obj1.func.bind(obj2), 1500);

   →setTimeout(obj1.func.bind(obj1), 1000): obj1 객체의 func 메서드가 콜백함수로 쓰여 함수의 형태가 되고, this 객체를 주지 않았으면 전역객체가 this로 할당되었겠지만, bind를 통해 obj1 객체를 bind해주어 this에 obj1객체가 할당됨.

   →따라서, obj1의 name, 'obj1'이 출력됨

 

   →setTimeout(obj1.func.bind(obj2), 1500): obj1 객체의 func 메서드에 obj2 객체를 bind하여 사용하여, this에 obj2 객체가 할당됨

   →따라서, obj2의 name, 'obj2'가 출력됨

·회원가입 화면에서 id, name, pw, pw확인을 입력한 뒤 SIGN UP 버튼을 누르면 서버에서 입력된 데이터를 파일에 저장하는 로직

 

1. index.js 파일에 "/register" 경로에 post로 요청이 오면 ctrl.process.register 함수가 실행되도록 요청되어 있음

// index.js

router.post('/register', ctrl.process.register);

 

2. home.ctrl.js 파일에서 process내에 register 함수를 보면 user클래스 내의 reigster가 실행되도록 되어 있음

// home.ctrl.js

const process = {
    login: async (req, res) => {
        const user = new User(req.body);
        const response = await user.login();
        return res.json(response);
    },
    
    register: (req, res) => {
        const user = new User(req.body);
        const response = user.register();
        return res.json(response);
    },
};

 

3. User.js 파일의 user 클래스에서 register()함수를 보면 save메서드를 UserStorage 파일에서 save 메서드를 호출하도록 되어 있음

// User.js에서 user클래스 내부

register(){
  const client = this.body;
  const response = UserStorage.save(client); // constructor에서 넘겨준 body를 그대로 저장
  return response;
}

 

4. UserStorage.js에서 save메서드를 "/register"경로에서 입력받은 값을 파일에 저장하는 메서드로 구현

// UserStorage.js


// save 메서드를 실행할 때 users 데이터 전체를 넘겨주기 위해 코드 수정
static getUsers(isAll, ...fields) {
  return fs
    .readFile("./src/databases/users.json") // 해당 경로에서 파일 읽어오기
    .then((data) => {  // 파일 읽어오는 것을 성공하면
      return this.#getUsers(data, isAll, fields);  // 은닉화하여 생성한 함수 #getUsers 실행
    })
    .catch(console.error);  // 파일 읽기에 실패하면 error 반환
}


// getUsers 메서드에서 파일 읽어오는 것에 성공했을 때 시행할 명령 정리
static #getUsers(data, isAll, fields) {
  const users = JSON.parse(data);
  if (isAll) return users; // isAll이 true이면 전체 데이터 바로 반환하기
  
  const userUsers = fields.reduce((newUsers, field) => { // 각각의 id, pw, name 필드 각각 반환
    if (users.hasOwnProperty(field)) {
      newUsers[field] = users[field];
    }
    return newUsers;
  }, {});
  return userUsers;
}


// 데이터를 그냥 추가하면 원래의 데이터가 사라지고 덮어씌워지므로,
// 원래의 데이터를 불러와서 새로운 데이터를 추가하고,
// 새로운 데이터를 추가한 데이터 전체를 파일에 저장해야됨
// users 데이터를 모두 불러오는 메서드(위에 있는 getUsers) 사용하여 getUsers가 반환한 모든 파라미터 다 받아오기

// 데이터를 읽어오는데 시간이 걸리므로 다 읽어올 때 까지 기다리도록 async 함수로 선언하고 getUsers 메서드는 await로 처리
static async save(userInfo) {
  const users = await this.getUsers(true);
  
  if (users.id.includes(userInfo.id)) {
    throw "이미 존재하는 아이디입니다."; // error를 통해 명령하면 object로 반환되므로 문자열 그대로 넘겨주기
  }
  users.id.push(userInfo.id);
  users.name.push(userInfo.name);
  users.pw.push(userInfo.pw);
  fs.writeFile("./src/databases/users.json", JSON.stringify(users));
  return { success: true };
}

 

 

5. 위에서 회원가입을 실행하기 위해 거치는 다른 파일들에도 asynx, awiat를 걸어 파일을 읽어오는 시간동안 기다리도록 명령

// User.js의 user 클래스 내부

async register(){ // async 함수로 변경
  const client = this.body;
  const response = await UserStorage.save(client); // awiat 처리
  return response;
}


// home.ctrl.js

const process = {
    login: async (req, res) => {
        const user = new User(req.body);
        const response = await user.login();
        return res.json(response);
    },
    
    register: async (req, res) => { // async 함수로 변경
        const user = new User(req.body);
        const response = await user.register(); // awiat 처리
        return res.json(response);
    },
};

 

 

6. 또한, User.js 파일에서 register 메서드는 이미 존재하는 아이디에 대한 예외 처리 코드 추가

// User.js의 user 클래스 내부

async register() {
  const client = this.body;
  try {
    const response = await UserStorage.save(client);
    return response;
  } catch (err) { // 에러 발생 시, UserStorage에서 throw해준 메세지, "이미 존재하는 아이디입니다."를 출력
    const a = { success: false, msg: err };
    console.log(a.msg);
    return a;
  }
}

 

 

7. 결과

 -서버 실행 후 "/register" 경로에서 아이디, 이름, 비밀번호, 비밀번호 확인 입력 후 SIGN UP을 누르면, users.json파일에 해당 정보 저장

 

 -원래 users.json

 -회원가입

 -회원가입 후 users.json(회원가입 창에서 새로 입력하고 SIGN UP을 누른 아이디, 이름, 비밀번호가 추가로 저장되어 있음)

1. UserStorage.js에 있던 사용자 id, pw, name 정보를 파일로 따로 만들기

 -src 폴더 안에 DB용 폴더 생성(src > databases > user.json)

 -user.json파일에 is, pw, name을 담고 있던 사용자 객체를 json 형식으로 복사(각 변수의 이름을 문자열로 처리해주기)

  // user.json

        {                
            "id": ["사용자1", "사용자2", "사용자3"],
            "pw": ["1234", "3456", "5678"],
            "name": ["이름1", "이름2", "이름3"]
        }

 

 

2. UserStorage.js 폴더에 있던 사용자 객체는 지우고 대신, 데이터 파일에 접근할 수 있도록 file system 불러오기

// UserStorage.js

const fs = require("fs")

 

 

3. UserStorage.js 폴더에 있던 #users객체를 지웠으므로 로그인을 위한 메서드였던 static getUserInfo()에 있던 users도 fs에서 불러오도록 변경

 -id와 id, pw, name이 저장된 파일을 불러온 뒤 비교하는 이후의 과정은 파일을 불러오는 fs.readFile이 성공했을 때 실행(.then)

 -그 외 에러가 나면 예외 처리(.catch)

// UserStorage.js (버전 1)

static getUserInfo(id) {
  return fs.readFile("./src/databases/users.json")
    .then((data) => {
      const users = JSON.parse(data); // json형태릐 데이터를 읽는 함수
      const idx = users.is.indexOf(id);
      const usersKeys = Object.keys(users);
      const userInfo = usersKeys.reduce((newUser, info) => {
        newUser[info] = users[info][idx];
        return newUser;
      }, {});
      return userInfo;
    })
    
    .catch(console.error);
}
// UserStorage.js (버전 2)(깔끔하게 함수로 정리한 버전)

static getUserInfo(id) {
  return fs.readFile("./src/databases/users.json")
    .then((data) => {
      return this.#getUserInfo(data, id);
    })
    
    .catch(console.error);
}

static #getUserInfo(data, id) { // 파일 불러오기 성공 시 수행되는 동작을 깔끔하게 함수로 묶어 빼주기
  const users = JSON.parse(data);
  const idx = users.is.indexOf(id);
  const usersKeys = Object.keys(users);
  const userInfo = usersKeys.reduce((newUser, info) => {
    newUser[info] = users[info][idx];
    return newUser;
  }, {});
  return userInfo;
}

 

 

4. User.js 파일에서 login()함수를 UserStorage함수가 바뀐 것에 따라 바꿔주기

// User.js

"use strict";

class User{
  // constructor 생략
  
  async login() {
    const client = this.body
    
    // UserStorage에서 getUserInfo를 실행할 때, json파일을 불러올 때 시간이 걸림
    // javascript는 비동기 처리를 하므로 json파일을 다 불러올 때까지 기다리지 않고 이 id, pw 비교 작업을 수행
    // 그에 따라 아직 불러오지 못한 json파일과 비교하며 undefined가 됨
    // 이를 방지하기 위해 await를 써서 UserStorage.getUserInfo가 다 시행될 때까지 기다리는 명령을 부여
    // 이때, await는 async 함수에서만 사용이 가능하므로 login() 함수를 async login()으로 변경
    const {id, pw} = await UserStorage.getUserInfo(client.id);
      if (id) {
        if (id === client.id && pw === client.pw) {
          return {success: true};
        }
        return {success: false, msg: "비밀번호가 틀렸습니다."};
      }
      return {success: false, msg: "존재하지 않는 아이디입니다."};
    }
  
  // register() 생략
}

module.exports = User;

 -await를 사용하지 않고 console.log(fs)를 통해 promise로 설정된 fs를 출력하면 promise: {<pending>}이 출력

  ※ "promise: {}": 데이터를 전부 읽어오지 못했다는 의미

 -또한, User.js파일에서 UserStorage.getUserInfo(client.id)를 console.log로 출력하면 아직 json파일을 불러오지 못해 id가 정의되지 않음

  →undefined가 뜸

 

 

5. controller인 home.ctrl.js파일에서도 login 메서드를 실행할 때 login 메서드가 User.js 파일 내에서 다 실행될 때까지 기다리도록 await 적용

// home.ctrl.js

const process = {
  login: async (req, res) => {
    const user = new User(req.body);
    const response = await user.login();
    return res.json(response);
  },
  
  //register 생략
};

 

+ Recent posts