5월 25일 TIL

·2023년 5월 24일
0

▶ JavaScript 문법 종합반 3주차 과제풀이

[1]
문제 설명 : 기존에 과제로 주어진 코드를 보고 가장 아래의 코드가 실행 되었을 때, “Passed ~” 가 출력되도록 getAge 함수를 채워주세요.
👩🏻 : 이 문제는 총 2가지 방법으로 풀게 되었다 첫 번째가 내가 쉽게 작성한 것이고 두번째는 문제 해설에서 객체를 만들어서 복사하는 방식으로 해야 한다는 것을 알게 된 후 푼 것이다. 강의를 보고 시간이 조금 지난 후에 문제를 풀어서 그런지 주차별로 정확히 어떤 내용으로 문제 풀이가 되었어야 하는지 혼동이 와서 이렇게 풀게 된 것 같다. 그래도 바로 풀어서 다행이다. 당연히 첫 번째 답안에서는 기존 객체 속 프로퍼티의 갯수가 많아지면 작성이 힘들어질 것이다. 문제를 풀거나 코드를 짤 때 이런 부분을 항상 유의해서 풀려고 하는데 저 때는 또 그 사실을 잠시 잊었나보다.. 이렇게 til을 쓰면서 다음 코드는 더 신경써서 작성해야지 라는 생각이 든다!

// 가장 아래의 코드가 실행 되었을 때, “Passed ~” 가 출력되도록 getAge 함수를 채워주세요

var user = {
  name: "john",
  age: 20,
};

var getAged = function (user, passedTime) {
  // 여기를 작성해 주세요!
  return {
    name: user.name,
    age: passedTime,
  };
};

var agedUser = getAged(user, 6);

var agedUserMustBeDifferentFromUser = function (user1, user2) {
  if (!user2) {
    console.log("Failed! user2 doesn't exist!");
  } else if (user1 !== user2) {
    console.log(
      "Passed! If you become older, you will be different from you in the past!"
    );
  } else {
    console.log("Failed! User same with past one");
  }
};

agedUserMustBeDifferentFromUser(user, agedUser);

// 가장 아래의 코드가 실행 되었을 때, “Passed ~” 가 출력되도록 getAge 함수를 채워주세요

var user = {
  name: "john",
  age: 20,
};

// 객체 만들어 프로퍼티 복사하기
var getAged = function (user, passedTime) {
  // 여기를 작성해 주세요!
  var user2 = {};
  for (prop in user) {
    user2[prop] = user[prop];
  }
  return user2;
};

var agedUser = getAged(user, 6);

var agedUserMustBeDifferentFromUser = function (user1, user2) {
  if (!user2) {
    console.log("Failed! user2 doesn't exist!");
  } else if (user1 !== user2) {
    console.log(
      "Passed! If you become older, you will be different from you in the past!"
    );
  } else {
    console.log("Failed! User same with past one");
  }
};

agedUserMustBeDifferentFromUser(user, agedUser);

[2]
문제 설명 : 출력의 결과를 제출해주세요, 그리고 그 이유를 최대한 상세히 설명해주세요.

var fullname = 'Ciryl Gane'

var fighter = {
    fullname: 'John Jones',
    opponent: {
        fullname: 'Francis Ngannou',
        getFullname: function () {
            return this.fullname;
        }
    },

    getName: function() {
        return this.fullname;
    },

    getFirstName: () => {
        return this.fullname.split(' ')[0];
    },

    getLastName: (function() {
        return this.fullname.split(' ')[1];
    })()

}

console.log('Not', fighter.opponent.getFullname(), 'VS', fighter.getName());
console.log('It is', fighter.getName(), 'VS', fighter.getFirstName(), fighter.getLastName);

2번째 문제 답안 :
(1)
Not Francis Ngannou VS John Jones 결과를 출력하는 console.log("Not", fighter.opponent.getFullname(), "VS", fighter.getName()); 문에서 fighter.opponent.getFullname()에 Francis Ngannou가 나온 이유는 getFullname 함수를 호출할 때 명확하게 그 주체가 지정되어 있기 때문입니다.
= fighter 객체안의 opponent 객체의 getFullname 함수를 실행하는데 그 안에서 return 해주는 this.fullname은 자신의 주체 fighter.opponent가 가지고 있는 fullname을 가리키게 됩니다.

fighter.getName()에 John Jones가 나온 이유는 getName을 호출하면 return해주는 this.fullname에서의 this는 자신의 주체인 fighter이고 fighter 객체의 요소인 fullname을 return해줘야 하기 때문에 이러한 결과가 나온 것입니다.

(2)
It is John Jones VS Ciryl Gane undefined 결과를 출력하는 console.log( "It is", fighter.getName(), "VS", fighter.getFirstName(), fighter.getLastName); 문에서 fighter.getName()에 John Jones가 나온 이유는 위의 1번과 동일하고

fighter.getFirstName()에 Ciryl Gane이 나온 이유는 getFirstName이 화살표 함수이고 화살표 함수는 this를 binding하지 않고 상위값이 this로 유지된다. 이를 실행하기 때문에 getFirstName의 return문에서의 this는 상위값인 fighter가 아니라 전역을 바라보게 되고 전역 객체에 있는 fullname 변수에 담긴 Ciryl Gane이 출력된다.

fighter.getLastName이 undefined가 나온 이유는 getLastName 뒤에 ( )괄호를 안붙여서 함수가 호출되지 않았기 때문에 아무것도 나오지 않았다.

👩🏻 : 이번 문제가 되게 햇갈리고 복잡해서 더 오래걸렸다.. 답이 주어져있어서 그래도 결과의 이유를 설명하기가 더 수월했는데 출력 결과를 바로 말해보라고 했으면 꽤나 오래걸렸을 것 같다. this에 대한 부분은 여전히 어렵다! 이 부분은 다음 진도를 위해서라도 다시 한번 강의를 열심히 들어야겠다.

▶ JavaScript 문법 종합반 4주차 - 콜백 함수

▶ 콜백함수 _ 기본 개념

# 다른 코드(forEach, setTimeout등)의 인자로 넘겨주는 함수
# 콜백 함수를 인자로 넘겨받는 주체인 forEach, setTimeout등은 콜백 함수를 필요에 따라 적절한 시점에 실행한다. =제어권을 가지고 있다.
action에 대한 제어권함수에게 있다.
# callback = 되돌아와서 호출하는 것
# 결론 :
제어권을 넘겨줄테니 너(주체)가 알고있는 그 로직으로 처리하라는 뜻
⭐ 콜백 함수는 다른 함수 또는 메서드에게 인자로 넘겨지면서 자신의 제어권 또한 함께 위임한 것이다.

▶ 콜백함수 _ 제어권

# 콜백 함수를 넘겨받는 주체는 무슨 제어권을 위임 받았는가?
(1) 호출 시점에 대한 제어권 - setInterval
setInterval은 매개변수로 받은 콜백함수 로직을 정해진 시간 간격으로 반복적으로 수행한다.
아래 예시에서 0.3초에 대한 제어권은 setInterval에게 있다.

var count=0;

var timer = setInterval(function(){
  console.log(count);

  if(++count>4) clearInterval(timer);
}, 300);

// 이 때 setInterval의 고유한 값이 timer에 담기게 되고 setInterval은 반복적으로 계속 실행되기 때문에
// 이를 멈추기 위해선 a를 멈춰줘야 해서 clearInterval(timer)가 필요하다.

(2) 인자에 대한 제어권 - map
map은 배열에 대한 api, 배열에 대한 method이다.
배열의 하나하나를 순회하면서 가공하여 새로운 배열을 return하는 역할을 한다.

var newArr = [10, 20, 30].map(function (currentVal, index) {
  // 현재 로직 안에서 수행되는 것에 의해 앞 배열의 요소들이 순회하면서 가공된 후 newArr에 할당된다.
  // 이때 callback 함수는 2가지 인자를 받는다.
  // 1. 배열을 순회하면서 가리키는 현재 요소 → currentVal
  // 2. index
  console.log(currentVal, index); // 현재 뭐가 담기는지 확인

  // map함수는 무조건 무언가를 배열의 크기만큼 할당해야 한다
  // return이 없으면 배열 안에는 undefined만 나온다.
  return currentVal + 5;
});

console.log(newArr);

# 메소드 등을 사용하여 원하는 규칙을 얻고자 한다면 그에 맞는 규칙에 따라서 작성해줘야 한다.
→ 메소드의 콜백함수 인자를 작성할 때 사람이 지정한 변수명은 컴퓨터의 동작에 영향이 없다. 컴퓨터는 인자로 들어갈 자리의 순서만 인식하고 동작하기 때문에 해당 메소드의 명세서에 나와있는 대로 작성해주어야 한다.

결론 : 사람이 인자에 대해서 제어하고 싶어서 원하는 이름의 순서를 바꿔도 사람은 인자를 제어할 수 없다. 제어는 해당 콜백함수를 만든 메소드(예를 들어 map)만이 콜백함수에 대한 인자의 제어권을 가지고 있다.
사람은 제어권이 넘어갈 함수의 규칙에 맞게 호출해야 한다.

(3) this
이전 학습 내용 : 콜백함수도 함수이기 때문에 기본적으로 this가 전역객체를 참조한다.
하지만 예외로 제어권을 넘겨받은 코드에서는 콜백 함수에 별도로 this가 될 대상을 지정한 경우 그 대상을 참조한다.
예외에 대한 예시로 나온 부분

document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
	console.log(this, e);
});

· addEventListener는 내부에서 콜백 함수를 호출할 때, call 메서드의 첫번째 인자에 addEventListener메서드의 this를 그대로 넘겨주도록 정의돼 있다. (상속)

· 위의 예시는 어떻게 가능할까? 이것을 이해하기 위해서 직접 별도의 this를 지정하는 로직을 구현해 보면 된다.
map 함수 직접 구현 ↓

// map 함수처럼 호출할 수 있게 나만의 map함수인 myMap을 만든다.
// 이렇게 만들어놓으면 어떤 배열에서든지 [ ].myMap으로 사용 가능하다.
// 기존 map함수가 항상 첫번째 인자로 콜백 함수를 받기 때문에 callback을 넣었다.
// 기존 map함수가 항상 두번째 인자로 this를 받기 때문에 thisArg을 넣었다.
// map 함수는 결론적으로 새로운 객체를 return하기 때문에 그 객체를 미리 로직에 선언
// 로직 for문 안에서 this.length에서의 this는 이 안에서는 myMap이 호출 주체이기 때문에 예시로는 [1, 2. 3]이 this가 된다.
// for문 안에 mappedValue에는 콜백함수를 수행할 값이 들어간다. 그런데 callback()을 수행할 때 this binding을 해주기 위해서 call을 사용한다.
// ㄴ callback.call(thisArg || global, this[i]); this binding할 값으로 thisArg가 들어왔으면 이것으로 해주고 아니면 global로 this를 지정해주라는 뜻
//   ㄴ  this[i]는 몇 번째 인지 모르지만 그 for문에 해당되는 인자를 넣어달라는 뜻
// mappedArr[i] = mappedValue; 위에서 작성한 것을 mappedArr에 있는 i번째 에다가 mappedValue를 넣어달라는 뜻
// return mappedArr; for문을 돌면서 완성된 배열을 return해 달라는 뜻

Array.prototype.myMap = function (callback, thisArg) {
  // map 함수에서 return할 결과 배열
  var mappedArr = [];

  for (var i = 0; i < this.length; i++) {
    var mappedValue = callback.call(thisArg || global, this[i]);
    mappedArr[i] = mappedValue;
  }

  return mappedArr;
};

// 실제 map 함수처럼 사용해보자.
// myMap이 map함수처럼 잘 작동하게 되면 그 결과를 받기 위해서 newArr도 선언해줬다.
var newArr = [1, 2, 3].myMap(function (number) {
  return number * 2;
});

console.log(newArr);

이런식으로 예외사항을 만들어 냈을 것이라고 유추할 수 있다!

▶ 콜백함수 _ 콜백 함수는 함수다

# 이때까지 콜백함수에 대한 이야기의 논지 : 콜백함수도 함수다
→ 콜백 함수도 함수로서의 호출로 인식이 돼서 기본적으로 this binding을 전역 객체(window, global)로서 한다. → 맥락을 잃어버리게 된다.(내부적 맥락이 아닌 전역객체를 바라보게 된다.)

(1) 객체의 메서드 전달 시 this
예시 코드로 설명 ↓ 주석참고!

// obj의 속성
// 1. vals라는 배열을 가졌다.
// 2. logValues 라는 함수를 가졌다.

var obj = {
  vals: [1, 2, 3],
  logValues: function (v, i) {
    console.log(" >>> test start");
    // this가 global이면 너무 많은 내용이 console값으로 출력되기 때문에
    // 아래와 같은 로직을 추가해주었다.
    if (this === global) {
      console.log("this가 global입니다. 원하지 않는 결과!");
    } else {
      console.log(this, v, i);
    }
    console.log(" >>> test end");
  },
};

// obj를 메서드로서 호출
obj.logValues(1, 2);
// logValues의 주체가 obj임이 명확하기 때문에 실행 시 this에 obj가 출력된다.

// 예를 들기 위해 (1, 4)를 넣어놨는데 이 부분에서 함수 자체가 아닌 인자까지 넣어버리면
// 함수 자체가 아닌 함수를 실행한 결과를 넣는것이니 주의!
// [4, 5, 6].forEach(obj.logValues(1, 4));

// 현재 forEach안에 매개변수로 obj.logValues를 넣었기 때문에
// 함수 자체가 아니라 메서드를 넣었다고 생각할 수 있지만 메서드의 형태처럼 보이나 결국 obj의 logValues: 후에 나오는
// function부터 시작되는 함수 자체를 넣은 것이다. obj.logValues를 차용한 것 뿐이다!
[4, 5, 6].forEach(obj.logValues);

출력값 :

test start
{ vals: [ 1, 2, 3 ], logValues: [Function: logValues] } 1 2
test end
test start
this가 global입니다. 원하지 않는 결과!
test end
test start
this가 global입니다. 원하지 않는 결과!
test end
test start
this가 global입니다. 원하지 않는 결과!
test end

⭐ 항상 메서드로서의 호출이라고 할 때는 호출을 해야하는 것이지 매개 변수로 넘겨줬다고 해서 그것까지 인식할 수는 없다! 결국 함수로서의 호출이였다!

▶ 콜백함수 _ 콜백 함수 내부의 this에 다른 값 바인딩하기

# 콜백 함수 내부에서 this가 문맥에 맞는 객체를 바라보게 할 수는 없을까?
# 콜백 함수 내부의 this에 다른 값을 바인딩하는 방법

· 전통적 방식 (1단계) ↓

// this를 self에 할당한 다음 그 것을 출력하는 방식
// obj안에 함수를 가지는 속성 func가 있는데 이 func 함수는 바로 console을 찍지 않고 console을 찍는 함수를 return 하도록 되어있다.

// 이 내용을 이해하려면 closure라는 방식을 알아야 한다.
// clousure 방식 : 현재 함수가 끝났음에도 불구하고 영향력을 끼치는 방식
// return을 한 부분은 return 뒤 함수 부분인데 this를 참조하는 부분은 그 윗줄인 var self = this; 이다.
// → 우리가 아래 obj1.func()에서 참조하고 있는 부분은 return뒤 함수 부분인데 func안에서 쓰는 변수 self는 그 함수의 바깥에 있기 때문에 바깥에서 참조하고 있는 self가 아직 살아있다 라는 개념

var obj1 = {
  name: "obj1",
  func: function () {
    var self = this; //이 부분!
    return function () {
      console.log(self.name);
    };
  },
};

// callback 이라는 변수를 선언 후 obj1.func() 함수를 담은게 아니라 실행한 결과를 담은 것이다.
// → func 함수 자체가 아닌 return 값인 console을 찍는 함수를 callback 이라는 변수에 담았다.
// → 그렇기 때문에 현재 this가 self에 담긴 채로 유지가  되는 것이다.
var callback = obj1.func();
setTimeout(callback, 1000);

출력값 : obj1
문제점 :
이 방식은 완전하게 this를 사용한 것이 아니고 번거로운 방식이다.
단순히 함수만 전달한 것이기 때문에, obj1 객체와는 상관이 없다.
메서드가 아닌 함수로서 호출한 것과 동일하다.

· 전통적 방식 대체 (2단계) ↓

// setTimeout에 obj1.func를 넣어주었는데 실제 func의 함수 내용을 보면 console에서 obj1.name을 바로 찍어주고 있다.
// 이렇게 되면 결과만을 위한 코딩 → 하드 코딩이 된다. 100점 중 10점짜리 코딩.. 지양해야 한다!
var obj1 = {
  name: "obj1",
  func: function () {
    console.log(obj1.name);
  },
};

setTimeout(obj1.func, 1000);

출력값 : obj1
문제점 :
전통적 방식보다 간결하지만 this를 사용하지 않게 되면서 결과만을 위한 코딩이 되었다.
이렇게 되면 this를 활용하여 다양한 것을 할 수 있는 목적 및 장점을 놓치게 된다.

· 전통적 방식 대체 (3단계) / 재활용 ↓
번거로워 보일 수는 있지만 this를 우회적으로나마 활용하여 원하는 결과를 얻을 수 있다.

// 전통적 방식을 차용해서 재활용하는 방식 (1)
// obj2의 func에 obj1.func를 넣어줌으로써 조금 더 간결하게 사용 가능하다.
var obj2 = {
  name: "obj2",
  func: obj1.func,
};

var callback2 = obj2.func();
setTimeout(callback2, 1500);

//전통적 방식을 차용해서 재활용하는 방식 (2)

// obj3는 name property만을 가지고 있다.
var obj3 = { name: "obj3" };

// callback3를 즉시 실행 함수로 만들기 위해 obj1.func를 즉시 실행하도록 call()으로 제어하면서 동시에
// obj3를 this binding 할 수 있도록 넘겨주었다.
var callback3 = obj1.func.call(obj3);
setTimeout(callback3, 2000);

· bind 메서드의 활용 방식 2가지 ← 가장 좋은 방법👍🏻

// 함수를 즉시 실행 함수가 아닌 this를 binding 하는 방법 : bind
// bind : 함수를 만들어 놓을 때 유용하다.

// 1번째 방법
// this를 binding 하는 방법
// 원래 선언해 놓은 this를 bind 한 것

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

// this를 유지하도록 만들 것이다.
// setTimeout에 첫번째 인자의 함수 부분에 bind를 이용해서 새로운 함수를 return 해주는 방법
// obj1.func에다가 bind를 묶어서 괄호안에 원하는 this를 넣어준다.
// 괄호 안에 obj1을 넣어주면 위의 obj1 객체와 this로서 묶이게 된다.
setTimeout(obj1.func.bind(obj1), 1000);

// 2번째 방법
// 함수 자체를 binding 하는 방법
// 1번째 방법의 this 뿐만이 아닌 어떤 this든 원하는대로 bind 할 수 있다는 것

var obj2 = { name: "obj2" };

// obj1.func를 실행할 때는 무조건 this는 obj2를 bind 한다.
// 위에 있는 obj1을 bind한게 아니고 새로운 객체의 새로운 this를 bind한 것이다.
setTimeout(obj1.func.bind(obj2), 1500);

▶ 콜백함수 _ 콜백 지옥과 비동기 제어

# 콜백 : 매개변수로 들어가는 함수여서 익명 함수라는 특징
# 콜백지옥 : 익명함수에 익명함수 또 그 익명함수에 익명함수 라는 식으로 계속 매개변수에 익명함수가 들어가다보면 들여쓰기가 너무 많아진다. 그 들여쓰기가 계속 깊어지다보면 들여쓰기 수준이 hell 수준이라고 해서 지옥이라고 부르기 시작해서 콜백지옥이라고 부른다.
# 콜백지옥은 주로 이벤트 처리서버 통신과 같은 비동기적 작업을 수행할 때 발생한다.
# 큰 문제점을 꼽자면 가독성과 유지보수의 문제가 있다.

# 동기 vs 비동기

  1. 동기 (synchronous)
    이미지 설명 : 앞사람의 주문이 끝나고 음료가 만들어지고 받아가는 순간까지 다음 사람이 기다리고 있다.

    · 현재 실행 중인 코드가 끝나야 다음 코드 실행
    · CPU 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적 코드
    · 계산이 복잡해서 CPU의 계산 시간이 오래걸리는 코드도 동기적 코드
    · 일의 순서가 중요!

  2. 비동기 (asynchronous)
    이미지 설명 : 시간이 얼마 걸리지 않는 주문받는 일을 미리 처리하고 커피 제조등 시간이 소요되는 일은 뒤에서 담당해주며 순서에 상관없이 제조가 완료되면 해당 고객을 불러서 음료를 건네준다. 여러가지는 한 번에 처리한다.

    · 현재 실행 중인 코드 완료 여부랑 무관하게 즉시 다음 코드 실행
    · setTimeout, addEventListener 등
    · 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 모두 비동기적 코드
    · 복잡도가 올라가면 비동깆거 코드의 비중이 늘어난다!

# 콜백지옥의 예시

// 4개의 setTimeout 함수가 엮여있는 콜백지옥을 흉내낸 코드

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,
  "에스프레소"
);

# 콜백지옥의 해결방안

  1. 기명 함수로 변환하는 방법
    기명함수 = 이름이 있는 함수
    단점 : 어차피 익명 함수를 쓸 거여서 한번 밖에 사용을 안하는데 각 함수마다 이름을 붙여주어서 인적 리소스 낭비가 크다.
    근본적인 해결책 X
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, '에스프레소');

+ 아래 내용에 들어가기 전에 왜 중첩을 해야만 하는가에 대한 이해가 있으면 좋다.
비동기작업은 순서를 보장하지 않는다는 특징이 있다.
그렇기 때문에 언제 결과의 제어권이 나한테 다시 올지 모른다는 것이 특징
setTimeout에 있는 시간은 setTimeout의 입장인 것이고 제어권을 넘겨준 코드 입장에서는 알 수 없다.
setTimeout이 아닌 서버 클라이언트 통신, 외부 API통신 (예를 들어 날씨 데이터를 얻어오는 함수) 같은 것들은 순서를 보장할 수 없다.

해결방안 1번 또한 근본적인 해결책은 아니고 이러한 경우 때문에 자바스크립트느 비동기적 작업을 동기적으로 즉, 동기적인 것 처럼 보이도록 처리해주는 장치를 계속해서 마련해주고 있다.→ Promise, Generator(ES6), async/await(ES7)

  1. 비동기 작업의 동기적 표현
    비동기 작업은 순서를 보장하지 않지만 순서를 보장하는 것이 필요하다.
    (1) Promise_1
    비동기 처리에 대해, 처리가 끝나면 알려달라는 약속이다.
    · new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 바로 실행된다.
    · 내부의 2가지를 기억하면 된다. (resolve, reject)
    ▷ resolve = 성공했다.
    발생 상황 : 서버 클라이언트 통신, 외부 API 통신 등을 통해서 정보를 잘 얻어온 경우

▷ reject = 실패했다.
발생 상황 : 서버가 폭파되거나 다운되서 응답을 줄 수 없는 경우

위의 처리 결과(resolve||reject)에 따라서 Promise.then(다음), Promise.catch(오류)로 오류 사항들을 tracking 할 수 있다.
따라서 비동기 작업이 완료 될 때 비로소 resolve, reject와 같은 처리를 할 수 있다.

이 방법으로 우리는 진보된 수준으로 비동기 작업의 동기적 표현을 구현할 수 있다.

위의 예시를 Promise를 이용한 방법으로 다시 작성 ↓

// 새로운 Promise를 만들고 인자로 바로 실행되는 콜백 함수를 만들어 준다.
// 그 콜백함수의 인자로 resolve나 reject 를 넣을 수 있다.
// 여기서는 우리는 실패가 아닌 성공을 해야하기 때문에 resolve를 넣어주었다.
// → 내부에서 들어간 setTimeout이기 때문에, 성공됐을 때 다음으로 넘어가게끔 하는 로직을 넣는다.
// resolve가 실행 되면 then(다음)으로 넘어간다.

new Promise(function (resolve) {
  setTimeout(function () {
    var name = "에스프레소";
    console.log(name);

    // resolve가 실행 되면 then(다음)으로 넘어가게끔 세팅 / resolve로 했다는 얘기는 then으로 넘어간다는 말
    resolve(name);
  }, 500);
  // then으로 넘어가는 방법
  // Promise로 열고 닫은 거 다음에 점(.)하고 바로 then()이라고 작성하고
  // 이 안에도 마찬가지로 콜백 함수가 들어간다.
  // 이 때 콜백 함수의 인자에는 resolve로 넣어놨던 name이 들어간다. 하지만 로직 안에서 사용하는 name과 헷갈리지 않기 위해
  // prevName 이라고 지정해줬다.
})
  .then(function (prevName) {
    // 이 안에서도 새롭게 Promise를 만들어준다. → 체이닝을 걸어준다.
    return new Promise(function (resolve) {
      setTimeout(function () {
        // 여기서 우리는 그냥 name을 출력하는 것이 아니라 위에서 받아서 prevName을 받아서 출력해야 된다.
        var name = prevName + ", 아메리카노";
        console.log(name);
        resolve(name);
      }, 500);
    });
  })
  .then(function (prevName) {
    // 이 안에서도 새롭게 Promise를 만들어준다. → 체이닝을 걸어준다.
    return new Promise(function (resolve) {
      setTimeout(function () {
        // 여기서 우리는 그냥 name을 출력하는 것이 아니라 위에서 받아서 prevName을 받아서 출력해야 된다.
        var name = prevName + ", 카페모카";
        console.log(name);
        resolve(name);
      }, 500);
    });
  })
  .then(function (prevName) {
    // 이 안에서도 새롭게 Promise를 만들어준다. → 체이닝을 걸어준다.
    return new Promise(function (resolve) {
      setTimeout(function () {
        // 여기서 우리는 그냥 name을 출력하는 것이 아니라 위에서 받아서 prevName을 받아서 출력해야 된다.
        var name = prevName + ", 카페라떼";
        console.log(name);
        resolve(name);
      }, 500);
    });
  });

(2) Promise_2
· refactoring : 다시 짓는다. / 비효율적인 코드를 효율적인 코드로 변경한다.
· Promise_1에서의 반복적인 코드들을 return해주는 함수를 만들어서 그 함수만 호출해주는 코드를 작성

// 결국 커피이름을 반복해서 출력하는 것
// 커피를 추가한다는 의미의 addCoffee 변수
// 기존에 then()에 있는 콜백함수를 호출할 때 계속 변수로 넣어줘야 하는 것(바뀌는 부분)은 커피 이름이였다.
// 커피 이름들을 계속 변수로 넣을 수 있도록 addCoffee의 function의 인자 안에 name을 넣어줬다.
// 그 name을 기반으로 내부의 로직을 생성하게끔 한다.
// 기존 코드에서 반복되는 로직을 return에 넣어주고 (= 이 함수를 호출했을 때 그 결과로 받을 거라는 뜻)
//  우리가 받아온 name만 기존 커피 이름이 담겨있던 문자열 자리에 넣어주면 된다.

// var addCoffee = function (name) {
var addCoffee =  (name) => { // 화살표 함수로 작성
  return function (prevName) {
    return new Promise(function (resolve) {
      setTimeout(function () {
        // 여기서 우리는 그냥 name을 출력하는 것이 아니라 위에서 받아서 prevName을 받아서 출력해야 된다.
        // var name = prevName + ", " +name;
        var newName = prevName ? `${prevName}, ${name}` : name; // 백틱을 사용하여 윗 줄보다 더욱 직관적으로 작성
        console.log(newName);
        resolve(newName);
      }, 500);
    });
  };
};

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

(3) Generator
· Generator를 이해하기 위해서는 Iterable 객체는 반복된다라는 개념과 동일
· Generator라는 문법은 기본적으로 Iterable 객체를 반환한다. → next() 메서드를 가지고 있으면 된다.
· 결론적으로 Generator라는 것은 반복할 수 있는 Iterable 객체를 생성한다.
· Iterable 객체는 next() 메서드를 가지고 있고 그래서 이 메서드로 자기 자신에 있는 요소들을 계속해서 순환할 수 있는 구조로 되어 있다. → 하나하나 마다 순환하면서 어떠한 작업을 수행하기 용이하다.
· Generator 함수와 Iterable 객체를 이해할 때 가장 많이 나오는 키워드는 yield 이다.
yield = 양보하다, 미루다 /
· 순서를 기다리지 않는 비동기적인 것들을 우리가 양보하고 미루며 기다리게 하는 역할을 하려고 Generator를 사용한다.
· 비동기 작업이 완료되는 시점마다 next 메서드를 호출해주면 Generator 함수 내부소스가 위 -> 아래로 순차적으로 진행 될 수 있다.

⭐ 계속 생각해봐야 하는 것! 왜 비동기적인 것들을 동기적으로 바꾸려고 하느냐?
→ 순서를 보장할 수 없기 때문에 순서 보장에 필요한 로직에서 순서를 보장 받기 위해서

· Generator를 사용한 예시 ↓

// 흐름 순서 1 : 제너레이터 함수 안에서 쓸 addCoffee 함수 선언
var addCoffee = function (prevName, name) {
  setTimeout(function () {
    coffeeMaker.next(prevName ? prevName + ", " + name : name);
  }, 500);
};

// *표 모양이 붙은 함수가 Generator 함수이다!
// 이 함수를 실행하면 Iterator 객체가 반환된다.
// yield 키워드를 만나면 멈춘다. - stop을 걸어준다.
// 그 후 문장이 끝날때가지 멈추고 다시 next 메서드로 호출되면 멈췄던 부분까지 또 가는 방식으로 진행된다.
// → 상단의 addCoffee라는 로직 안에 next 메서드를 이용한 로직이 있기 때문에.

// 흐름 순서 2 : Generator 함수 선언!
// yield 키워드로 순서 제어
var coffeeGenerator = function* () {
  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);
};

// Generator 함수인 아래 줄처럼 coffeeGenerator를 실행하면
// coffeeMaker는 Iterator 객체를 가지게 된다.
// 그래서 이 객체에 next() 메서드를 이용해서 어떠한 작업을 할 수 있다!
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();

(4) Promise + Async/await
· 이전에 썼던 방법 : then(그러면~) = 실행되고 나면. 그러면~
· 이번에 쓸 방법 : async / await
▷ async (비동기)
▷ await (기다리다)

· Promise + Async/await를 사용한 예시 ↓

// Promise를 return 해주는 함수

// coffeeMaker 함수에서 호출할 함수 'addCoffee'를 선언한다. 이 함수는 Promise를 반환한다.

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

// 아래 var coffeeMaker = async function () {} 가 화살표 함수라면 var coffeeMaker = async () => {}

// 중요! coffeeMaker 의 function 키워드 앞에 async라는 키워드를 붙여주었고
// async라는 키워드를 붙여주게 되면 function의 중괄호에 해당하는 이 스코프 안에
// await 키워드르 만난 그 메서드는 그 메서드가 끝날 때까지 무조건 기다리게 되어 있다.
// 근데 그 메서드는 항상 Promise를 반환해야만 한다.
// Promise를 반환하는 그 조건으로!  함수인 경우 await를 만나면 끝날 때까지 기다린다는 것
// 위에서 아래로 차례대로 내려오는데 async의 스코프 안에서 차례대로 밑으로 내려 오면서 await을 만나는 순간 해당 함수가
// 멈출때까지 기다린다.
// 그 조건은 _addCoffee라는 함수가 Promise를 반환한다는 전제 하에.
// 원래의 비동기라면 async를 만나더라도 바로 밑으로 내려가야하는데 await을 만나서 출력이 다 될 때까지
// 기다린 다음에 밑으로 진행된다.

var coffeeMaker = async function () {
  var coffeeList = "";
  var _addCoffee = async function (name) {
    coffeeList += (coffeeList ? ", " : "") + (await addCoffee(name));
  };
  // _addCoffee("에스프레소") 로직이 실행되는데 100초가 걸렸다.
  await _addCoffee("에스프레소");

  // 이 console.log가 100초 뒤에 실행된다.
  console.log(coffeeList);

  await _addCoffee("아메리카노");
  console.log(coffeeList);
  await _addCoffee("카페모카");
  console.log(coffeeList);
  await _addCoffee("카페라떼");
  console.log(coffeeList);
};

// 함수를 실행하는 로직
coffeeMaker();
profile
코린한별

0개의 댓글