개발공부/자바스크립트

[JavaScript] 스터디 5일차_ 콜백 함수

햄❤️ 2021. 6. 23. 16:01
반응형

1. 콜백함수

 - 어떤 함수 X를 호출하면서 "특정조건" 일 때, 함수 Y를 실행해서 나에게 알려달라는 요청을 보낸다. 이 요청을 받은 함수 X의 입장에서는 해당 조건이 갖춰줬는지 여부를 스스로 판단하고 Y를 직접 호출한다.

즉, 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 그 제어권도 함께 위임한 함수이다. 콜백함수를 위임받은 코드는 자체적인 내부 로직에 의해 이 콜백 함수를 적절한 시점에 실행한다.

 

2. 제어권

  2-1) 호출시점

  - 아래 예시에서, setInterval에 전달한 첫번째 인자인 cbFunc 함수가 콜백함이다. setInterval 함수는 제어권을 넘겨받고, 스스로 판단에따라 0.3초마다 이 익명함수를 실행한다. 즉 setInterval은 콜백 함수 호출 시점에 대한 제어권을 가진다.

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

var timer = setInterval(cbFunc, 300);

//0 0.3초
//1 0.6초
//2 0.9초
//3 1.2초
//4 1.5초

 2-2) 인자

  - map 메서드의 구조: 첫번째 인자로 콜백함수를 받고, 생략가능한 두 번째 인자로 콜백함수 내부에서 this 로 인식할 대상 지정(생략시에는 전역객체 바인딩) 

 - 콜백함수의 첫번째 인자에는 배열 요소중 현재값, 두번째 인자에는 현재값의 인덱스, 세번째 인자는 map 메서드의 대상이 되는 배열 자체가 담김

Array.prototype.map(callback[, this Arg])
callback: function(currentValue, index, array)

 

콜백함수를 호출하는 주체가 사용자가 아닌 map 메서드므로, 인자에 어떤 값들을 어떤 순서로 넘길것인지가 전적으로 map 메서드에 달려있다. 즉,  어떤 순서 로 넘길것인지에 대한 제어권이 있다.

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]

2-3) this

 - 콜백함수는 기본적으로 함수라 this는 전액객체를 참조하지만, 제어권을 넘겨받을 코드에서 콜백함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조한다.

 - addEventListener의 경우, 여기서 this를 소환하면 이것은 바로 e.currentTarget(지금 이벤트가 동작하는 곳)이라는 뜻과 똑같은 의미 = addEventListener 부착된 HTML 요소(클릭 버튼)이다.

setTimeout(function(){ console.log(this);}, 300); // window {...}

[1,2,3,4,5].forEach(function(x){
	console.log(this); // window {...}
});

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

3. 콜백함수는 함수다

 - 콜백함수로 어떤 객체의 메서드를 전달하면, 그 메서드는 메서드가 아니라 함수로서 호출된다.
    어떤 함수의 인자에 객체의 메서드를 전달해도, 결국 메서드가 아니라 함수임

var obj = {
	vals: [1,2,3],
    logValues: function(v,i){
    	console.log(this,v,i);
    }
};
//메서드 호출이므로 obj 객체를 this로 가리킴
obj.logValues(1,2); //{vals: Array(3), logValues: ƒ} 1 2
[4,5,6].forEach(obj.logValues) //obj.logValues가 가리키는 함수만 전달하였으므로 window가 this가 됨

//Window {...} 4 0
//Window {...} 5 1
//Window {...} 6 2

 

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

  - 콜백함수 내부에서 this가 객체를 바라보게 하고 싶다면, ES5에 나온 bind 메서드를 쓰는 방법이 있다.

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

setTimeout(obj1.func.bind(obj1), 1000); // this를 obj1로 바인딩했으므로 obj1 출력
var obj2 = { name: 'obj2' };
setTimeout(obj1.func.bind(obj2), 1500); // this를 obj2로 바인딩했으므로 obj2 출력

 

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

 - 비동기: 현재 실행중인 코드 완료 여부와 무관하게 즉시 다음 코드로 넘어간다.( = 요청과 응답이 동시에 이루어지지 않는다)

 - 동기: 현재 실행중인 코드가 완료된 후에 다음 코드를 실행하는 방식 ( = 요청과 응답이 동시에 이루어짐)

- 비동기방식의 필요성?

  화면에서 서버로 데이터를 요청했을때 서버가 언제 그 요청에 대한 응답을 줄지 모르는데 계속 기다릴 수 없고, 텅 빈 화면을 보여줄 수 없으니깐!

  ▪setTimeout(특정 시간이 경과되기 전까지 어떤 함수 실행을 보류)

  ▪addEventListener(사용자의 직접적인 개입이 있을 때 비로소 어떤 함수를 실행하도록 대기)

  ▪XMLHttpRequest(웹브라우저 자체가 아닌 별도의 대상에 무언가를 요청하고 그에 대한 응답이 왔을 때 비로소 어떤 함수를 실행하도록 대기)

별도의 요청, 실행대기, 보류등과 관련된 코드는 비동기적 코드이다.

 

🌟콜백지옥의 예시 

setTimeout(function (name) {
  let 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, '에스프레소');

 

🔸 콜백지옥 해결 - 기명함수로 변환

 - 코드의 가독성을 높이고, 함수 선언과 호출을 구분할 수 있다면 위에서 아래로 읽어내려가는데 어려움이 없음

let coffeeList = '';

const addEspresso = function (name) {
  coffeeList = name;
  console.log(coffeeList); // "에스프레소"
  setTimeout(addAmericano, 500, '아메리카노');
};

const addAmericano = function (name) {
  coffeeList += ', ' + name;
  console.log(coffeeList); // "에스프레소, 아메리카노"
  setTimeout(addMocha, 500, '카페모카');
};

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

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

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

 

5-1) 비동기작업의 동기적 표현 - promise

 - new 연산자와 함께 호출한 promise의 인자로 넘겨주는 콜백함수는 호출할 때 바로 실행, 그 내부에 resolve 또는 reject 함수를 호출하는 구문이 있으면 둘 중 하나가 실행되기 전까지는 다음 then or catch 구문으로 넘어가지 않는다.

 - Promise 객체는 3가지 상태를 가진다.

  • 대기(pending) : 아직 실행되지 않은 초기 상태
  • 이행(fulfilled) : 작업이 성공적으로 완료됨.
  • 거부(rejected) : 작업이 실패함.
const addCoffee = function (name) {
  return function (prevName) {
    return new Promise(function (resolve) {
      setTimeout(function () {
        const newName = prevName ? (prevName + ', ' + name) : name;
        console.log(newName);
        resolve(newName);
      }, 500);
    });
  }
};

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

 

5-2) 비동기작업의 동기적 표현 - Generator

 - ES6에서 등장, *가 붙은 함수

 - Generator 함수를 실행하면 Iterator가 반환되며, next라는 메서드를 가지고 있다. next 메서드를 호출하면 Generator 내부에서 제일 처음 등장하는 yield 함수의 실행을 멈추고, next를 한번 더 호출하면 그 다음 등장 yield 함수를 멈춘다

 - next()는 항상 아래 두 프로퍼티를 가진 객체를 반환

  • value: 산출 값
  • done: 함수 코드 실행이 끝났으면 true, 아니라면 false
const addCoffee = function (prevName, name) {
  setTimeout(function () {
    coffeeMaker.next(prevName ? prevName + ', ' + name : name);
  }, 500);
};

const coffeeGenerator = function* () {
  const espresso = yield addCoffee('', '에스프레소');
  console.log(espresso);
  
  const americano = yield addCoffee(espresso, '아메리카노');
  console.log(americano);
  
  const mocha = yield addCoffee(americano, '카페모카');
  console.log(mocha);
  
  const latte = yield addCoffee(mocha, '카페라떼');
  console.log(latte);
};

const coffeeMaker = coffeeGenerator();
coffeeMaker.next();

 

5-3) 비동기작업의 동기적 표현 - Promise + Async/await

 - 자바스크립트의 비동기 처리 패턴 중 가장 최근에(ES2017) 나온 문법이며, 기존의 비동기 처리 방식인 콜백 함수와 프로미스의 단점을 보완함

 - 비동기 작업을 수행하고자 하는 함수 앞에 async를 표기하고, 함수 내부에서 실질적인 비동기 작업이 필요한 위치마다 await 을 표기해야 뒤에 내용을 Promise로 자동 전환하고, 해당 내용이 resolve된 이후에 다음으로 진행

async function 함수명() {
  await 비동기 처리 메서드명 //HTTP 통신을 하는 비동기 처리 코드 앞, 일반적으로 AXIOS API 호출 코드
}
const addCoffee = function (name) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(name);
    }, 500);
  });
};

const coffeeMaker = async function () {
  let coffeeList = '';
  const _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();

 

반응형