📝

콜백함수

1. 콜백 함수

1.1 콜백 함수 정의

콜백이란 어떠한 요청이 발생할 때 응답하는 행위로 전달하는 동작을 의미하며, 콜백 함수란 요청을 받고 실행되는 함수이다. 특정 함수에 매개변수로 전달되는 함수를 콜백 함수라고 하고 콜백 함수를 전달 받아 호출하는 함수를 고차 함수라고 한다. 이어달리기로 비유하자면 1번 주자가 2번 주자에게 바통을 넘겨줘야 출발할 수 있는 것처럼 콜백 함수는 어떠한 동작 후 실행되는 함수이다. 매개변수로 함수를 전달 받아 나중에 호출한다는 것이 전체적인 콜백 함수의 개념이다.

1.2 콜백 함수 예제

function 인사(인사말){ console.log("인사해야지....!"); 인사말() } 인사(()=>{console.log("안녕하세요!🙏")})
위 예제에서 콜백 함수인사말이고, 고차 함수인사이다. 인사 함수가 호출될 때, 인사 함수 인수로 전달된 “안녕하세요!”를 출력하는 콜백 함수가 실행된다.

1.2.1 filter() 메서드에서 활용되고 있는 콜백 함수

filter(콜백함수) // filter 기본 형식, 조건에 맞는 새로운 배열 반환 const 오늘할일 = [{ '장소' : '집', '할일' : '밥 먹기' }, { '장소' : '집', '할일' : '김치 먹기' }, { '장소' : '회사', '할일' : '삼겹살 먹기' }, { '장소' : '회사', '할일' : '갈비 먹기' }, { '장소' : '헬스장', '할일' : '운동 하기' }]; const 집선택하는콜백함수 = (오늘할일) => (오늘할일.장소 === '집') // 집 검색하는 함수 작성 const 집에서할일 = 오늘할일.filter(집선택하는콜백함수) // filter에 콜백 함수로 사용 console.log(집에서할일) // 출력값 [{ '장소' : '집', '할일' : '밥 먹기' }, { '장소' : '집', '할일' : '김치 먹기' }]

2. 콜백 함수 동기 처리 방식

2.1 동기 처리의 특징

순차적으로 하나씩 코드가 실행되기 때문에 작업 순서가 보장되며 직관적이다. 오래 걸리는 작업이 실행될 경우에는 그 작업이 완료될 때까지 기다렸다가 다음 코드를 실행하기 때문에 시간이 지연된다는 단점이 있다.

2.2 동기 처리 예제

console.log("반갑습니다!👋"); // 1 function 인사(인사말){ // 2 인사말() // 4 } 인사(()=>console.log("안녕하세요!🙏")) // 3 console.log("또 봬요!🙌") // 5
위 예제 코드의 예상 콜백 함수 실행 순서는 아래와 같다.
  1. console.log(”반갑습니다!👋”)
  1. function 인사(인사말){…}
  1. 인사(()=>console.log("안녕하세요!🙏"))
  1. 인사말()
  1. console.log("또 봬요!🙌")
 
console.log("반갑습니다!👋"); // 1 function 인사(인사말){ 인사말() // 3 } 인사(()=>console.log("안녕하세요!🙏")) // 2 console.log("또 봬요!🙌") // 4
하지만, 실제 실행 순서는 함수 선언부가 런타임 시점 이전에 호이스팅 되어 실행 순서에 포함되지 않고 아래와 같은 순서로 콜백 함수가 실행된다.
  1. console.log(”반갑습니다!👋”)
  1. 인사(()=>console.log("안녕하세요!🙏"))
  1. 인사말()
  1. console.log("또 봬요!🙌")

3. 콜백 함수 비동기 처리 방식

3.1 비동기 처리의 특징과 한계

비동기 처리의 특징은 실행 결과를 기다리지 않고 바로 다음 코드가 실행되는 것이다. 병렬적으로 처리되기 때문에 기다리는 시간이 줄고, 이는 사용자 경험 개선과 연결이 된다. 하지만, 코드가 복잡해지고 비동기 처리가 많아지면 실행 순서를 예측하기 어렵다는 단점이 있다.

3.2 비동기 처리 예제

setTimeout(콜백함수, 호출시간); // setTimeout 구성(호출시간 생략 가능) console.log("반갑습니다!👋") // 1 setTimeout(()=>{console.log("안녕하세요!🙏")},3000) // 2 console.log("또 봬요!🙌") // 3
setTimeout이 비동기 실행 함수이기 때문에 결과를 반환 받기 전 다음 코드를 실행하여 “반갑습니다!👋”, “또 봬요!🙌”, “안녕하세요!🙏”의 순서로 출력된다.
setTimeout 함수의 2번째 인수에 딜레이를 주지 않을 경우, 아래와 같이 출력된다.
console.log("1번 손님 들어오세요."); // 1 setTimeout(() => {console.log("2번 손님 들어오세요.")}, 0); // 3 console.log("3번 손님 들어오세요."); // 2
예제에서 2번째 인수에 0을 넣어서 딜레이를 주지 않았지만 결과는 마찬가지로 비동기 실행 함수 뒤의 코드가 먼저 실행이 되어 다음과 같은 순서로 출력이 된다.
  1. 1번 손님 들어오세요.
  1. 3번 손님 들어오세요.
  1. 2번 손님 들어오세요.
 
let 이름; function 이름설정() { setTimeout(() => { 이름 = "유"; }, 1000); return 이름; } const 사용자이름= 이름설정(); console.log(사용자이름); // undefined
위 예제는 1초 뒤에 이름이라는 변수 안에 ‘유’라는 값을 할당해서 콘솔창에 출력하고 싶은데, setTimeout은 비동기 실행 함수이므로 작업 완료 이전에 return문이 먼저 실행이 되어 우리가 의도한 ‘유’가 아닌, undefined이 출력된다.

3.3 콜백 함수를 통한 비동기 처리의 한계 해결 방법

비동기 처리는 작업 순서를 보장 받기 어렵다는 문제가 있다. 이를 아래 예제 처럼 콜백 함수를 사용해 해결할 수 있다.
let 이름; function 이름설정(이름출력) { setTimeout(() => { 이름= "유"; 이름출력(이름); }, 1000); } const 사용자이름 = 이름설정(function(name) { console.log(이름); }); console.log(사용자이름); // 출력 결과 : 유
이름설정에서 이름을 반환하지 않고, 이름 인수를 받아 콘솔창에 출력하는 이름출력 이라는 콜백 함수를 만들어서 setTimeout 내부에 넣어주었다. 우리가 의도한대로 “유”가 잘 출력되었다.

4. 콜백 지옥

4.1 정의

콜백 함수의 사용이 증가하여 들여쓰기가 많이 발생하는 현상을 의미한다. 한두 개의 작업이라면 모르겠지만, 여러 비동기 작업들을 동기적으로 처리하려면 그만큼의 콜백 함수가 사용되고, 콜백 함수 사용이 많아지면 들여쓰기가 많아지는만큼 가독성이 크게 떨어진다.

4.2 콜백 지옥 예제

콜백지옥1(function(n1){ 콜백지옥2(n1, function(n2){ 콜백지옥3(n2, function(n3){ 콜백지옥4(n3, function(n4){ 콜백지옥5(n4, function(n5){ 콜백지옥6(n5, function(n6){ console.log(n6) }) }) }) }) }) })
위 예제는 극단적인 콜백 지옥으로 코드가 많이 중첩되어있기 때문에 디버깅이 어려워 코드 수정이 매우 어려워진다.
 
setTimeout(function () { let 이름리스트 = '유, '; setTimeout(function () { 이름리스트 += '김, '; setTimeout(function () { 이름리스트 += '최'; console.log(이름리스트); }, 0); }, 0); }, 0); // 출력 결과 : 유, 김, 최
콜백 함수가 실행된 결과를 다음 콜백 함수에 전달하는 작업이 반복된 예시이다. 콜백 함수가 추가된다고 가정하면 매우 복잡한 콜백 지옥이 된다.

4.3 콜백 지옥 해결

기존 코드를 기명 함수로 변경하여 콜백 지옥을 해결할 수도 있고 Promiseasync-await 사용하여 콜백 지옥을 해결할 수 있다. Promiseasync-await 은 이어지는 각 챕터에서 자세히 설명하고 있다.

4.3.1 기명 함수로 콜백 지옥 해결 예시

let 이름리스트 = ''; const 유씨 = function (이름리스트){ 이름리스트 += '유, '; 김씨(이름리스트) } const 김씨= function (이름리스트){ 이름리스트 += '김, ' 최씨(이름리스트) } const 최씨= function (이름리스트){ 이름리스트 += '최' console.log(이름리스트) } 유씨(이름리스트) // 출력 결과 : 유, 김, 최
출력 결과는 이전 예제와 동일하지만 가독성이 더 좋고 비교적 쉽게 코드 수정이 가능하다.
익명 함수를 사용한다면 함수 내부에서 다른 함수를 연속적으로 호출해줘야 하지만, 전역에서 기명 함수로 선언함에 따라서 콜백 지옥이 발생하지 않아 코드가 간결하다.