wakelight23 2024. 11. 8. 20:53

callback 함수

● 콜백함수에 대해서

  - 다른 코드의 인자로 넘겨주는 함수 (forEach, setTimeout)

  - callback = call(부르다) + back(되돌아오다) = 되돌아와서 호출하는 것

    즉, 제어에 대한 권한을 줄테니 입력된 로직으로 처리해라

  - 다른 코드(함수 or 메서드)에 인자로 넘겨줌으로 제어권도 함께 위임한 함수

  - 자체적으로 내부 로직에 적절한 시점에 실행

 

● 제어권

 1. 호출 시점

let count = 0;

// timer : 콜백 내부에서 사용할 수 있는 '어떤 게 돌고있는지' 알려주는 id값
let timer = setInterval(function() {
	console.log(count);
	if(++count > 4) clearInterval(timer);
}, 300);
let count = 0;
let cbFunc = function () {
	console.log(count);
	if (++count > 4) clearInterval(timer);
};
let timer = setInterval(cbFunc, 300);

// 실행 결과
// 0 (0.3sec)
// 1 (0.6sec)
// 2 (0.9sec)
// 3 (1.2sec)
// 4 (1.5sec)

 - 원래 cbFunc() 수행하면 호출주체제어권은 모두 사용자

 - setInterval에 넘겨주면 호출주체제어권은 모두 setInterval

code 호출 주체 제어권
cbFunc(); 사용자 사용자
setInterval(cbFunc(), 300); setInterval setInterval

 

 2. this

 - 제어권을 넘겨받을 코드에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우.
    → 그 대상을 참조

// 예시 Array.prototype.map
Array.prototype.mapaaa = function (callback, thisArg) {
  let mappedArr = [];

  for (let i = 0; i < this.length; i++) {
    // call의 첫 번째 인자는 thisArg가 존재하는 경우는 그 객체, 없으면 전역객체
    // call의 두 번째 인자는 this가 배열일 것(호출의 주체가 배열)이므로,
		// i번째 요소를 넣어서 인자로 전달
    let mappedValue = callback.call(thisArg || global, this[i]);
    mappedArr[i] = mappedValue;
  }
  return mappedArr;
};

const a = [1, 2, 3].mapaaa((item) => {
  return item * 2;
});

console.log(a);

 - 제어권을 남겨받을 코드에서 call/apply 메서드의 첫 번 째 인자에서
   콜백 함수 내부에서 사용될 this를 명시적으로 binding 하기 때문에
    → this에 다른 값이 담길 수 있는 것.

 

● 콜백 함수는 함수

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

 

● 콜백 함수 내부의 this에 다른 값 바인딩(binding)

 - 가장 좋은 방법 : bind 메서드 활용

let obj1 = {
	name: 'obj1',
	func: function () {
		console.log(this.name);
	}
};
//함수 자체를 obj1에 바인딩
//obj1.func를 실행할 때 무조건 this는 obj1로 고정
setTimeout(obj1.func.bind(obj1), 1000);

var obj2 = { name: 'obj2' };
//함수 자체를 obj2에 바인딩
//obj1.func를 실행할 때 무조건 this는 obj2로 고정
setTimeout(obj1.func.bind(obj2), 1500);

 

● 콜백 지옥 ★

 - 콜백 함수를 익명 함수로 전달하는 과정이 반복되어
   코드의 들여쓰기가 보기에 매우 나쁜 경우 (어지럽고 혼돈스러워서 지옥)

 - 왜 일어날까? = 이벤트 처리 및 서버 통신과 같은 비동기적 작업을 수행할 때 발생

 - 뭐가 문젠데? = 가독성이 나쁘고 유지보수가 힘듦

 

 - 동기(synchronous)

   1) 현재 실행중인 코드 끝나면 다음 코드 실행

   2) CPU 계산에 의해 즉시 처리가 가능한 대부분의 코드 = 동기적 코드

   3) CPU가 계산하는 데에 오래 걸리는 코드 = 동기적 코드

 

 - 비동기(asynchronous => async)

   1) 실행 중인 코드의 완료 여부 무관하게 즉시 다음 코드로. 

   2) setTimeout, addEventListner 등

   3) 별도의 요청, 실행 대기, 보류 등과 관련된 코드 모두 비동기적 코드

(출처 : https://velog.io/@mrbartrns/til-16-asynchronous-of-js )

                      → 동기, 비동기를 매우 적절하게 설명한 이미지

 

 - 콜백 지옥의 예시 ▼

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

 

- 해결 방안

  → Promise = 비동기 처리에 대해 처리가 끝나면 알려준다는 '약속(promise)'

       - new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 즉시 실행

       - 내부의 resolve(or reject) 함수 호출 구문이 있을 경우, 

         둘 주 하나가 실행되기 전까지는 다음(then), 오류(catch)로 넘어가지 않음

       - 비동기 작업이 완료될 때 resolve, reject 호출

       - 표현하는 방법 

       Generator ▼

var addCoffee = function (prevName, name) {
	setTimeout(function () {
		coffeeMaker.next(prevName ? prevName + ', ' + name : name);
	}, 500);
};
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);
};
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();

       

        Promise ~then + 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();