웹개발/javascript

[javascript] 콜백함수와 동기/비동기 처리

쌀쌀 2024. 1. 9. 16:53

콜백함수

 

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

 

예시)

// setTimeout
setTimeout(function() {
  console.log("Hello, world!");
}, 1000);

// forEach
const numbers = [1, 2, 3, 4, 5];

numbers.forEach(function(number) {
  console.log(number);
});

 

2. 콜백함수를 넘겨받은 코드 forEach, setTImeout 등은 콜백 함수를 필요에 따라 적절함 시점에 실행하게 된다.(제어권을 가짐)

3. callback = call(부르다)+back(되돌아오다) = 되돌아와서 호출해라

   => 제어권을 넘겨줄테니 니가 알고 있는 그 로직으로 처리해달라

4. 즉, 콜백 함수는 다른 코드(함수 또는 메서드)에게 인자로 넘겨줌으로써 제어권도 함께 위임한 함수이다.

콜백 함수를 위임받은 코드는 자체적으로 내부 로직에 의해 이 콜백함수를 적절한 시점에 실행한다. 

 

 

제어권

 

그렇다면 콜백함수를 넘겨받은 코드는 어떤 제어권을 갖게 될까?

 

1. 호출시점

콜백 함수의 제어권을 넘겨받은 코드는 콜백 함수 호출 시점에 대한 제어권을 가진다.

 

예시1

var count = 0;

var timer = setInterval(function(){
  console.log(count);
  if(++count > 4 ) clearInterval(timer)
 },300)
 
 //0.3초 후에 콜백함수를 호출

 

예시2

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

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

원래 cnFunc()를 수행한다면 그 호출주체와 제어권 모두 사용자가 됨

setInterval로 넘겨주게 되면 그 호출주체와 제어권 모두 setInterval이 됨

 

2. 인자 

 

map함수는 각 배열 요소를 변환하여 새로운 배열을 반환한다. 

// map 함수에 의해 새로운 배열을 생성해서 newArr에 담고 있네요!
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 함수의 parameter는 콜백함수를 넘겨받은 map 메서드에게 그 제어권이 있다.

 

3. this

콜백 함수도 함수이기 때문에 기본적으로는 this가 전역객체를 참조한다.

그러나 예외가 있다.

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

Array.prototype.mapaaa = function(callback, thisArg){
   var mappedArr = [];
  
   for (var i =0; i< this.length; i++){
  	 // call의 첫 번째 인자는 thisArg가 존재하는 경우는 그 객체, 없으면 전역객체
     // call의 두 번째 인자는 this가 배열일 것(호출의 주체가 배열)이므로,
     // i번째 요소를 넣어서 인자로 전달
     var 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에 다른 값이 담길 수 있다.

 

콜백함수는 함수다.

 

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

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

//method로써 호출
obj.logValues(1, 2);

//callback => obj를 this로 하는 메서드를 그대로 전달한게 아니에요
//단지, obj.logValues가 가리키는 함수만 전달한거에요(obj 객체와는 연관이 없습니다)
[4, 5, 6].forEach(obj.logValues);

 

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

var 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);

 

콜백 지옥과 비동기 제어

 

콜백지옥이란?

 1. 콜백 함수를 익명 함수로 전달하는 과정이 반복되어 코드의 들여쓰기 수준이 헬 수준인 경우

 2. 주로 이벤트 처리 및 서버 통신과 같은 비동기적 작업을 수행할 때 발생

 3. 가독성이 나쁘고, 수정이 어렵다

 

 

 

동기 : synchronous

  • 현재 실행중인 코드가 끝나야 다음 코드를 실행하는 방식
  • CPU의 계산에 의해 즉시 처리가 가능한 대부분의 코드는 동기적 코드
  • 계산이 복잡해서 CPU가 계산하는 데에 오래 걸리는 코드 역시도 동기적 코드

 

비동기 : a + synchronous ⇒ async

  • 실행 중인 코드의 완료 여부와 무관하게 즉시 다음 코드로 넘어가는 방식
  • 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. Promise

 

Promise는 비동기 처리에 대해, 처리가 끝나면 알려달라는 ‘약속’

  • new 연산자로 호출한 Promise의 인자로 넘어가는 콜백은 바로 실행됨.
  • 그 내부의 resolve(또는 reject) 함수를 호출하는 구문이 있을 경우 resolve(또는 reject) 둘 중 하나가 실행되기 전까지는 다음(then), 오류(catch)로 넘어가지 않아요.
  • 따라서, 비동기작업이 완료될 때 비로소 resolve, reject 호출해요.

 

예시

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('카페라떼'));
​

 

해결 2. 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();