[JS] 자바스크립트의 콜백함수

baegyeong·2024년 7월 21일

Java Script

목록 보기
5/9
post-thumbnail

코어자바스크립트 ch4. 콜백함수를 읽고 제 생각과 함께 정리한 내용입니다.


콜백함수의 this 지정

콜백함수도 함수이기 때문에 기본적으로는 this가 전역객체를 참조하지만, 제어권을 넘겨받을 코드에서 콜백함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조하게 된다 (p.100)

  • 즉, 코드에서 this를 별도로 지정하지 않았다면 전역객체를 참조한다.
    [1, 2, 3, 4, 5].forEach(function (x) {
    	console.log(this); // Window
    }
    • 해당 코드에서도 this는 전역객체를 참조한다. 제어권을 넘겨받은 forEach가 별도로 this가 될 대상을 지정하지 않았기 때문이다.
    • 만약 forEach(callbackFn, thisArg)에 따라서, this를 지정했다면 전역객체를 참조하지 않을 것이다.

메서드를 콜백함수로 전달한 경우

var obj = {
	vals: [1, 2, 3],
	logValues: function(v, i){
		console.log(this, v, i);
	}
};
obj.logValues(1, 2);
[4, 5, 6].forEach(obj.logValues);
  • obj.logValues라는 객체의 메서드가 콜백함수로 전달했다.
  • 이는 ‘함수’로서 호출된다. 그러므로 this는 전역객체를 가리킨다. (obj를 가리키는 게 아니다!)
  • 왜일까? 결국엔 obj에 있는 logValues를 호출한 거 아닐까?
    • 하지만 이 코드의 제어권은 forEach가 갖고 있다. 즉 this의 제어권도 forEach가 갖고 있다.
    • 따라서 이 코드의 this는 전역객체를 가리킨다.

고차함수와 콜백함수

고차함수는 함수를 인자로 전달하거나, 결과로 함수를 반환하는 함수이다.

고차함수는 매개변수를 통해 전달받은 콜백함수의 호출 시점을 결정해서 호출한다.

고차함수에 콜백함수를 전달할 때 콜백 함수를 호출하지 않고 함수 자체를 전달해야 한다.

function funcA(callback) {
	callback()
}

function funcB(){
	console.log('콜백함수 B')
}

console.log(funcA(funcB)) // '콜백함수 B'

이 코드에서 funcA는 콜백함수를 인수로 받는 고차함수이고, funcB는 콜백함수로서 호출되고 있다.

고차함수는 외부 상태 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에 기반을 두고 있다.

즉, 고차함수는 함수형 프로그래밍에서 유용하게 쓰일 수 있다. 왜그럴까?

  • 함수형 프로그래밍은 순수함수와 보조함수의 조합
    • 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성 해결
    • 변수의 사용을 억제하여 상태 변경을 피하려는 프로그램 패러다임
    • 조건문, 반복문, 변수는 오류 발생의 근본적인 원인이 될 수 있다.
  • 그래서, 함수형 프로그래밍은 결국 순수 함수를 통해 사이드이펙트를 억제하고 프로그램의 안정성을 높인다.
function sumOfSquares(arr) {
  let sum = 0;
  for (let i = 0; i < arr.length; i++) {
      sum += arr[i] * arr[i];
  }
  return sum;
}

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = sumOfSquares(numbers);
console.log(result);
  • 조건문과 반복문을 사용한 코드
function sumOfEvenSquares(arr) {
  return arr
    .map((num) => num * num) // 제곱
    .reduce((acc, num) => acc + num, 0); // 합계 계산
}

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = sumOfEvenSquares(numbers);
console.log(result); // 출력: 220
  • 고차함수를 사용한 코드

비동기

  • 비동기: 동기가 아닌 것. 즉 현재 실행 중인 코드가 완료 된 후에 실행되는 게 아닌, 완료여부를 고려하지 않은 것
    • setTimeout
    • 사용자의 직접적인 개입이 있을 때(addEventListener)
    • 무언가 요청 후 응답이 왔을 때 어떤 함수를 실행하도록 대기(XMLHttpRequest)
  • 위의 사례들에서 콜백함수를 사용하면, 실행 중인 코드가 완료된 후 이후의 동작을 실행시킬 수 있음
    function getData() {
    	let tableData;
    	axios.get('https://domain.com/products/1', function (response) {
    		tableData = response;
    	})'
    	return tableData;
    }
    
    console.log(getData()); // undefined
    • 콜백을 사용하지 않으면 비동기로 처리되어 데이터를 불러오기전에 tableData를 반환한다.

      function getData(callbackFunc) {
      	let tableData;
      	axios.get('https://domain.com/products/1', function (response) {
      		callbackFunc(response);
      	});
      }
      
      getData(function(tableData) {
      	console.log(tableData);
      }
    • axios.get의 response 데이터가 callback에 전달되어 실행된다.

  • 그러나 순차적인 비동기 로직개선을 위해 콜백함수를 연속해서 사용하면, 콜백 지옥에 빠질 수 있다.
  • 교재에 나온 것처럼 기명함수로 변환하여 콜백지옥을 해결할 수 있지만, es6이후 도입된 promise, async/await에 대해서도 공부해보자.

promise와 async/await

promise

비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냄

  • 단어 뜻 그대로 미래의 어떤 시점에 결과를 제공하겠다는 ‘프로미스’를 반환한다.
  • 프로미스의 상태
    • pending: 비동기 처리 로직이 아직 완료되지 않은 상태
    • fulfuilled: 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
    • rejected: 비동기 처리가 실패하거나 오류가 발생한 상태
new Promise(function(resolve, reject) {
	// ...
}
  • resolve(value): value와 함께 호출
  • reject(error): 에러 발생 시 에러객체를 나타내는 error와 함께 호출

Promise.all()

순회 가능한 객체에 주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 promise를 반환한다.

  • 입력값으로 들어온 프로미스 중 하나라도 rejected 된다면 promise.all()은 즉시 거부한다.
  • Promise.settled()는 모든 프로미스가 완료될 때까지 기다린다.

async/await

  • async/await는 promise를 더 편하게 사용할 수 있다.

  • promise의 단점을 보완

    • promise를 사용해 then()을 길게 이어나가면 가독성이 떨어지고, 에러 발생시 확인이 어렵다.
  • async: promise를 반환한다는 것을 선언

  • await: 해당 promise의 상태가 바뀔 때까지 기다림

    • then()과 같은 역할을 한다.
    • async와 같이 쓰이기 때문에 await는 최상위 코드 레벨에서 작동하지 않는다.
  • promise에서 하던 catch() 예외처리는 어떻게 할까?

    • async 함수 내에서 try/catch문을 사용한다.
    • throw new Error 구문을 사용한다.
async function f() {
  await Promise.reject(new Error("에러 발생!"));
}

async function f() {
  throw new Error("에러 발생!");
}
  • 위 아래 두 코드는 동일하게 동작한다.
async function handleSubmit() {
      try {
        const paymentData = await paymentWidget.requestPayment({
          orderId: "KOISABLdLiIzeM-VGU_8Z", // 주문 ID
          orderName: "토스 티셔츠 외 2건" // 주문명
        });
		console.log(paymentData);
        return paymentData;
      } catch (error) {
        console.log(error.message);
      }
    }

레퍼런스

자바스크립트 비동기 처리와 콜백함수

자바스크립트 promise 쉽게 이해하기

mdn promise

mdn promise.all

예제로 이해하는 await/async 문법

0개의 댓글