본문 바로가기
👩‍💻 Programming/JavaScript

[코어 자바스크립트] 4장 콜백 함수

by codingBear 2022. 11. 25.
728x90
반응형

01 콜백 함수(callback function)란?

👉 다른 함수 또는 메서드의 인자로 할당하면서 제어권도 함께 위임한 함수.

👉 어떤 함수 X를 호출하면서 '특정 조건일 때 콜백 함수 Y를 실행해서 나에게 알려달라'는 요청을 함께 보냄. 이후 함수 X는 해당 조건이 충족되는지 내부 로직으로 판단하여 콜백 함수 Y를 직접 호출함.


02 제어권

4-2-1 호출 시점

👉 setInterval 메서드의 구조는 위와 같다. 우선 scope에는 Window 객체 또는 Worker의 인스턴스가 들어오는데 일반적인 브라우저 환경에서는 window를 생략해서 함수처럼 사용 가능하다. 매개변수로는 콜백 함수와 딜레이(ms)를 반드시 전달해야 하고 세 번째부터는 선택사항이다.

👉 setInterval 메서드를 실행하면 반복적으로 실행되는 내용을 특정하는 고유한 ID값이 반환된다. 이를 변수에 담아 반복 실행되는 도중에 clearInterval을 활용해 종료할 수 있다.

 

👉 위 예제에서 setInterval은 cbFunc라는 콜백 함수의 제어권을 가지고 0.3초마다 콜백 함수를 실행한다. 이처럼 콜백 함수의 제어권을 넘겨받은 함수는 콜백 함수 호출 시점에 대한 제어권을 가진다.

 

4-2-2 인자

👉 map 메서드는 첫 번째 인자로 콜백 함수를 받고 선택 사항인 두 번째 인자로 콜백 함수 내부에서 this로 인식할 대상을 특정한다(생략 시 전역 객체 바인딩).

👉 배열 안의 요소를 하나씩 꺼내며 콜백 함수를 반복 호출하고, 콜백 함수의 결괏값들을 모아 새로운 배열을 반환한다. 콜백 함수의 첫 번째 인자에는 배열의 현재 요소, 두 번째 인자에는 현재 요소의 인덱스, 세 번째 인자에는 map 메서드의 대상인 배열이 담긴다.

 

4-2-3 this

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

 

💚 별도의 this를 지정하여 map 메서드를 구현한 예

👉 call/apply 메서드의 this에는 thisArg 값이 있을 경우는 그 값을, 없을 경우에는 전역 객체를 지정한다. 첫 번째 인자에는 this가 배열을 가리킬 것이므로 배열의 i번째 요소를, 두 번째 인자에는 i 값을, 세 번째 인자에는 배열 자체를 지정한다. 메서드의 결괏값이 변수 mappedValue에 담겨 mappedArr의 i 번째 값에 할당된다.

 

💚 콜백 함수 내부에서의 this 예시들


03 콜백 함수는 함수다

👉 어떤 함수의 인자에 객체의 메서드를 전달하더라도 이는 결국 메서드가 아닌 함수로서 호출된다. forEach 메서드의 인자로 객체의 메서드가 전달되면 해당 메서드는 더 이상 객체와 연관이 없어지고 함수를 호출했을 때와 마찬가지로 전역 객체를 this로 가리킨다.


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

👉 객체의 메서드를 콜백 함수로 전달했을 때 해당 객체를 this로 바라보게 하려면 this를 다른 변수에 담아 콜백 함수로 활용할 함수에서는 this 대신 그 변수를 사용하게 하고 이를 클로저로 만드는 방식을 쓸 수 있다.

👉 obj1.func 메서드 내부 self 변수에 this를 담고 익명 함수를 선언함과 동시에 반환한다. 10번째 줄 callback 변수에 obj1.func()가 담기고 11번째 줄에서 이 callback을 setTimeout 함수에 인자로 전달하면 1초 뒤에 callback이 실행되면서 'obj1'을 출력한다.

 

👉 예제 4-10은 예제 4-8의 func 함수를 재활용한 것이다. callback2에는 obj1의 func를 복사한 obj2의 func를 실행한 결과를 담아 콜백으로 사용한다. callback3의 경우 obj1의 func를 실행하면서 call 메서드로 this를 obj3가 되도록 지정해 콜백으로 사용한다. 이렇게 예제 4-8처럼 self 변수에 this를 우회적으로 담으면 다른 객체에서도 해당 this를 활용할 수 있다.

👉 예제 4-9의 경우 처음부터 바라볼 객체를 obj1으로 지정해놔서 다른 객체를 바라보게 할 수 없다. 재활용할 필요성이 없는 경우라면 이처럼 써도 무방하다.

 

💚 ES5 bind 메서드 활용하여 this 바인딩하는 예시


05 콜백 지옥과 비동기 제어

🤔 동기 vs 비동기

👉 동기: CPU 계산으로 인해 즉시 처리가 가능한 대부분의 코드는 동기적인 코드

👉 비동기: 별도의 요청, 실행 대기, 보류 등과 관련된 코드는 비동기적인 코드(例: setTimeout, addEventListener, XMLHttpRequest 등)

 

😲 콜백 지옥이란?

👉 콜백 함수를 익명 함수로 반복해서 전달하다 보니 코드의 들여쓰기가 감당하기 힘들 정도로 깊어지는 현상. 주로 비동기적인 코드를 작성하다 발생함.

 

👉 0.5초마다 커피 목록을 수집하고 출력한다는 목적에는 부합하나 들여쓰기 수준이 과해 가독성이 떨어짐.

 

💚 익명함수를 기명함수로 바꾸어 가독성을 높인 예

👉 가독성은 높아졌으나 일회성 함수를 전부 변수에 할당하는 것은 비효율적인 작업임.

 

💚 ES6 Promise를 사용하여 콜백 지옥을 해결한 예

👉 new 연산자와 함께 호출한 Promise의 인자에 넘겨주는 콜백 함수는 호출할 때 바로 실행된다. 하지만 콜백 함수 내부의 resolve 혹은 reject 함수 둘 중 하나라도 실행되지 않으면 then(다음) 또는 catch(오류 구문)으로 넘어가지 않는다. 

👉 따라서 비동기 작업이 완료될 때 resolve 혹은 reject를 호출하는 방법으로 비동기 작업의 동기적 표현이 가능.

 

💚 ES6 Generator를 사용하여 콜백 지옥을 해결한 예

👉 6번째 줄의 '*'이 붙은 함수가 Generator 함수이다. Generator 함수를 실행하면 Iterator가 반환되는데 이는 next라는 메서드를 가진다. next 메서드를 호출하면 Generator 함수 내부의 맨 처음 yield에서 함수 실행을 멈춘다. 이후 next 메서드를 다시 호출하면 앞선 멈췄던 지점에서 시작하여 다음 yield에서 함수 실행을 멈춘다.

👉비동기 작업이 완료되는 시점마다 next 메서드를 호출한다면 Generator 함수 내부의 소스가 위에서 아래로 순차적으로 실행됨.

 

💚 ES2017 async/await를 사용하여 콜백 지옥을 해결한 예

👉 비동기 작업을 수행하려는 함수 앞에 async를 표기하고 함수 내부의 비동기 작업이 필요한 위치마다 await를 표기한다. 이렇게 함으로써 뒤의 내용들이 Promise로 전환되고 해당 내용이 resolve된 이후 다음 작업이 진행됨. Promise의 then과 유사한 효과를 볼 수 있음.


06 정리

  • 콜백 함수는 다른 코드에 인자로 할당됨과 동시에 그 제어권도 함께 위임한 함수.
  • 제어권이라 함은?
    • 콜백 함수를 호출하는 시점
    • 콜백 함수를 호출할 때 인자로 넘겨줄 값들 및 순서
    • 콜백 함수의 this 지정 가능. 미지정 시 전역 객체. bind 메서드로 임의 지정 가능.
    • 콜백 함수 자리에 메서드를 전달해도 결국 함수로 실행됨.
    • 콜백 지옥을 해결하기 위한 방법으로는 Promise, Generator, async/await 등이 있다.
728x90
반응형

댓글