본문 바로가기
내일배움캠프 TIL

2023-10-16 본 캠프 8일차 / 27일차 TIL

by KMS_99 2023. 10. 17.

2023-10-13 본 캠프 8일차 / 27일차 TIL

내일배움캠프 : JavaScript 문법 종합반 4주차

1. 콜백함수

의미 : 다른 코드의 인자로 넘겨주는 함수. 인자로 넘겨준다는 말은 인자를 받는 주체가 있다는 말과 동일.

  • 예시 : forEach(콜백함수), setTimeout(콜백함수, 시간)

1-1. 콜백함수를 인자로 받는 주체는 두가지 제어권을 갖는다.

  • 호출 시점에 대한 제어권
    // setInterval() : 반복해서 매개변수를 매개변수로 받은 콜백함수의 로직을 수행
    var count = 0;
    var cbFunc = function(){
      console.log(count++);
      if (count>4) clearInterval(timer);
    }
    cbFunc(); // 제어권은 개발자에게 있다.
    var timer = setInterval(cbFunc, 300); // 제어권은 setInterval에게 있다.

  • 인자에 대한 제어권
    // map 함수
    // 콜백함수로 사용 될 때 컴퓨터는 해당 매개변수의 순서에 따라서 인식한다.
    // 따라서 콜백함수로 사용할 때 규칙에 따라서 매개변수를 사용해야한다.
    var newArr = [10,20,30].map(function(num,index){
      console.log(`${index}=>${num}`);
      return num+5;
    });
    // 매개변수 이름 순서 변경
    var newArr2 = [10,20,30].map(function(index,num){
      console.log(`${index}=>${num}`);
      return num+5;
    });
    console.log(newArr); // [ 15, 25, 35 ] => 원하는 정보 o
    console.log(newArr2); // [ 5, 6, 7 ] => 원하는 정보x

1-2 메서드를 콜백함수로 활용할 경우 메서드로 동작하지 않고 함수로 동작한다 (this 바인딩 -> 전역객체)

var obj = {
  vals: [1, 2, 3],
  logValues: function (v, i) {
    console.log(`this가 ${this}입니다. arg1:${v}, arg2:${i}`);
  },
};
// 메서드로 사용할 시 (this바인딩 -> 객체)
obj.logValues(1, 2); // this가 [object Object]입니다. arg1:1, arg2:2
// 매서드를 콜백함수로 활용을 했을경우
// 매서드로서 동작하지 않고 함수로 동작한다. (this 바인딩->전역객체)
[4].forEach(obj.logValues); // this가 [object global]입니다. arg1:4, arg2:0

1-3 콜백함수의 명시적 this 바인딩

콜백함수는 함수이기 때문에 기본적으로 전역객체를 this 바인딩한다. 이를 명시적으로 다른 객체를 바인딩 할 수 있으며 그중 bind를 이용한 this 바인딩이 가장 효율적이다.

var obj1 = {
    name : 'obj1',
    func : function(){
        console.log(this.name);
    }
}
// bind(임의의 객체)
var obj2 = obj1.func.bind({name:'kim'});
// bind(현재 객체)
var obj3 = obj1.func.bind(obj1);
setTimeout(obj1.func,1000); // undefined (this->전역객체)
setTimeout(obj2,1500); // kim (this->{name:'kim})
setTimeout(obj3,2000); // obj1 (this->obj1)


2. 동기 / 비동기

동기 (synchronous)

  • 현재 실행중인 코드가 끝나야 다음 코드를 실행하는 방식
  • CPU의 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기 코드

비동기 (asynchronous)

  • 현재 실행중인 코드와 무관하게 즉시 다음코드로 넘어가는 방식
  • 별도의 요청, 실행, 대기, 보류 등과 관련된 코드는 모두 비동기적 방식
  • setTimeout, addEventListner, 기타 서버통신 등

"주로 비동기 작업을 수행할 때 연속해서 콜백함수를 사용하게되면서 많은 들여쓰기가 일어나는 콜백지옥 현상이 나타난다."

콜백지옥 : 연속해서 콜백함수를 사용하면서 들여쓰기가 많이 일어나는 현상, 유지보수가 힘들어지며, 가독성이 떨어지는 문제 발생.

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. 비동기 작업의 동기적 표현

다음 프로그램 코드를 작성하는 상황을 가정해보자.

A서버에서 위치 정보를 호출하고 받아온 위치정보를 바탕으로 B서버에서 날씨정보를 얻어오는 프로그램

일의 순서는 A > B 순으로 이루어져야한다.

서버통신은 대표적인 비동기 코드이다.
하지만 비동기 코드는 일의 순서를 고려하지 않기 때문에 끝나는 시점을 개발자가 알 수 없다.

따라서 비록 비동기 코드이지만, 동기적 코드 처럼 순서를 예측하는 것처럼 보이게 하는 것이 필요하다.

이것이 바로 "비동기 작업의 동기적 표현" 이다.

자바스크립트는 업데이트를 계속하면서 비동기 작업의 동기적 표현을 효과적으로 표현하는 여러 방식을 도입하였다.


  • ES6 : Promise, Generator
  • ES7 : async/await


1-1. Promise

의미 : 사전적의미인 약속과 같으며, 처리가 끝나면 알려달라는 약속이다.

사용 :

  1. new 연산자로 호출한 Promise의 인자로 넘어가는 콜백함수는 바로 실행된다.
  2. 콜백함수의 내부에서 경우에 따라 resolve(성공)/reject(실패) 함수를 호출하며, 해당 함수가 호출되는 시점에 resolve는 then 이후에 있는 부분이실행되며, reject는 catch를 통해 오류가 접수된다.

  • Promise 함수의 사용 예
new Promise(function (resolve) {
  setTimeout(function () {
    var name = "에스프레소";
    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);
  }).then(function (prevName) {
    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);
      });
    });
  });
});

  • Promise 함수 리펙토링 예시
var name = '';
const 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-2. Generator

의미 : 이터러블 객체를 만들며, 이터러블 객체의 특징을 이용하여 순차적 코드실행이 되도록 한다.

사용 :

  1. 함수 선언시 function*의 형태로 선언 (이터러블 객체로 변환)
  2. 내부에 비동기적인 실행이 필요한 부분에 앞에 yield 키워드 사용
  3. 이터러블 객체는 next() 메서드를 통해 객체가 반환되기 때문에 next 메서드를 통해 실행
// *가 붙은 함수 => 제너레이터 함수
// 이 함수를 실행하면 이터레이터 객체가 반환된다.
// (1) 제너레이터 함수 안에서 쓸 addCoffee 함수 선언
var addCoffee = function(prevName, name) {
    setTimeout(function (){
        //반환값
        coffeeMaker.next(prevName ? `${prevName}, ${name}` : name);
    },500);
};
// (2) 제너레이터 함수 선언!
// yield로 순서 제어
var coffeeGenerator = function* () {
    // yield 뒤에 나온 코드를 끝날 때 까지 기다린다.
    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();
// 이터레이터 객체를 next를 통해서 순차적 실행
coffeeMaker.next();


1-3. async/await

사용 :

  1. 비동기 처리를 할 스코프에 async 선언
  2. 해당 스코프에 비동기 처리를 할 부분에 await 키워드 입력 (이때 promise를 반환해야한다.)
var addCoffee = function(name) {
    return new Promise(function (resolve) {
        setTimeout(function(){
            resolve(name);
        },500);
    });
};
// async가 선언된 스코프 내에서 await를 만나면 끝날때 까지 기다린다.
var coffeeMaker = async function () {
    var coffeeList="";
    var _addCoffee = async function (name) {
        coffeeList += (coffeeList ? ", ": "") + (await addCoffee(name));
    };
    // promise를 반환하는 함수일 경우, await를 만나면 무조건 끝날 때 까지 기다린다.
    await _addCoffee("에스프레소");
    console.log(coffeeList);
    await _addCoffee("아메리카노");
    console.log(coffeeList);
    await _addCoffee("카페모카");
    console.log(coffeeList);
    await _addCoffee("카페라떼");
    console.log(coffeeList);
};
coffeeMaker();