Callback (콜백 함수)
Callback (콜백 함수)란 ?
- JS에서 콜백함수는 매우 중요한 개념이다.
- Node.js 환경에서 프로그래밍 시 반드시 필요한 개념이기도 하다.
- 콜백 함수란 어떤 이벤트가 발생한 후, 수행될 함수를 의미한다.
- JS에서 함수는 1급객체이므로, 인자 전달 시 함수를 전달할 수 있기 때문에 콜백함수가 가능하다.
$(".submitBtn").click(function(){ | |
alert("제출버튼을 클릭했습니다!"); | |
}) |
┌ 위의 코드는 submitBtn 클래스를 가진 요소를 클릭했을 때, 콜백 함수가 실행되는 jQuery 코드이다.
├ 즉, 비동기 이벤트인 click에 대한 이벤트 리스너로 콜백 함수가 작성된 것이며, 그 내용은 alert()를 호출한다.
└ click의 콜백 함수는 네이밍을 할 필요가 없는 익명함수이고, 대부분의 콜백 함수는 이렇게 익명함수로 작성된다.
Callback (콜백 함수)는 클로저 ?
- 비동기 프로그래밍을 할때, 어떤 함수가 실행될 때 특정 시점에 콜백함수가 수행된다.
- 콜백함수는 자신이 포함된 함수의 환경에 접근이 가능하다.
- 클로저는 함수가 선언될 대의 환경을 기억하고 있으므로, 콜백함수는 클로저라고 할 수 있다.
function callback(cb){ | |
cb(); | |
} | |
function add(x, y){ | |
let sum = x + y; | |
callback( function(){ | |
console.log(sum); | |
}) | |
} | |
add(3,5); |
┌ 위의 소스코드는 콜백함수가 클로저라는 것을 보여준다.
├ add 함수 내부에 있는 callback 함수의 인자인 익명함수가 콜백함수이다.
├ add 함수는 3과 5를 인자로 더한 값을 sum에 할당하고, sum 변수를 콜백함수로 전달한다.
└ 콜백함수는 클로저이기때문에 오류를 발생하지 않고, sum 변수를 참조할 수 있다.
Callback Hell (콜백 지옥)
- 콜백함수를 남용하면 콜백지옥에 빠질 수 있다.

└ 위 사진은 if와 else를 남용하여 기이한 구조를 갖는 함수가 작성한 것이다.
Callback (콜백 함수) 남용 예시
function add(x, callback){ | |
let sum = x + x; | |
console.log(sum); | |
callback(sum); | |
} | |
add(3, function(result){ | |
add(result, function(result2){ | |
add(result2, function(result3){ | |
add(result3, function(result4){ | |
console.log("에너지 파") | |
}) | |
}) | |
}) | |
}) |
┌ 위의 코드는 콜백함수가 단순히 add() 함수를 호출하기 때문에 코드가 복잡해 보이지 않을 수도 있다.
├ 하지만 실무에서 로직을 짜다보면 이보다 훨씬 길 것이므로, 위의 코드는 답이 없는 코드가 된다..
└ 콜백함수는 유용하지만 이처럼 잘못 사용할 경우 콜백지옥을 보게 된다.
Callback Hell (콜백지옥)을 해결 할 수 있는 방법
① Promise

┌ MDN에 따르면 위와 같이 JS Promise를 설명하고 있다.
├ Promise 단어 그대로 나중에 연산하기 위한 "약속"과 같은 개념이다.
├ Promise는 비동기 요청에 대하여, 비동기 실행이 완료된 후 결과 값 또는 실패의 이유를 콜백 함수로 전달한다.
└ 따라서, 비동기 실행이 정상적으로 되었는지, 오류가 발생했는지 알 수 있도록 상태가 정의되어 있어야 한다.
상태
- fulfilled : 비동기 동작 정상 완료
- rejected : 비동기 동작 중 에러 발생
const test = function(bool){ | |
return new Promise(function(resolve, reject){ | |
setTimeout( function(){ | |
if(bool){ | |
resolve("fulfilled 상태입니다. then으로 연결됩니다."); | |
} | |
else{ | |
reject("rejected 상태입니다. catch로 연결됩니다."); | |
} | |
}, 1000) | |
}) | |
} | |
test(true) | |
.then( function(result){ | |
console.log(result); | |
}) | |
.catch( function(err){ | |
console.log(err) | |
}) |
┌ 위 예시는 promise를 사용한 간단한 예제이고, 각 상태가 어떻게 연결되는지 보여주고 있다.
├ Promise 생성자 함수에 콜백함수를 정의하는데, 매개변수로 resolve, reject를 정의한다.
├ 처리하는 로직상에서 비동기 처리가 정상적으로 수행되면 resolve() 함수를 호출하고,
├ 비동기 처리가 실패하면 reject() 함수를 호출한다.
├ Promise 생성자의 prototype에는 then(), catch() 메서드가 존재한다.
┌ then() 메서드는 호출한 Promise가 resolve()를 호출했을 때 반환값을 콜백함수로 받아주며,
├ 이 때 새로운 Promise를 반환한다.
├ catch() 메서드는 호출한 Promise가 reject()를 호출했을 때 반환값을 콜백함수로 받아주며,
└ 이 때 새로운 Promise를 반환한다.
├ 위의 예시는 test() 함수를 호출할 때 true를 인자로 전달했으므로 비동기 함수인 setTimeout()에서
├ resolve() 메서드가 실행되기 때문에 "fullfilled 상태입니다. then으로 연결됩니다" 문구가 출력된다.
② async / await
- 비동기 호출 후 이를 처리하는 콜백 함수의 개념은 매우 중요하다.
- 비동기 프로그래밍에서 콜백 함수는 반드시 사용해야 하는 부분이다.
- 하지만, 콜백 함수가 깊어지면 코드가 복잡해지므로(가독성 떨어짐) ES6에서 Promise를 도입했다.
- 하지만 Promise를 사용해도 여전히 코드가 복잡하자,
- ES8에서 async와 await을 도입했고, 덕분에 비동기 코드를 동기적으로 깔끔하게 처리할 수 있게 되었다.
- 그렇다고 항상 async / await이 옳다고는 할 수 없다.
ⓐ 콜백의 깊이가 깊지 않을 때는 작성하기 간편한 콜백함수를 호출하거나,
Promise를 사용하는 것이 더 나은 방법일 수 있다.
ⓑ async / await은 Promise를 사용하기 때문에 Promise를 알아야 하고,
async / wait이 할 수 없는 동작을 Promise로 해결할 수 있는 경우도 있다. - 따라서, Promise와 async / await을 때에 따라 사용하는 것이 중요하다.
async function foo(){ | |
await someAsyncFunction(){...} | |
await anotherAsyncFunction(){...} | |
} |
┌ 콜백함수를 사용하기 위해서 async와 await 키워드를 사용한다.
├ 함수 이름 앞에 async 키워드를,
├ 호출할 비동기 함수 앞에 await 키워드를 사용한다.
├ 이 때 핵심은 await이고, async는 단지 선언용이다. → 즉, 함수 앞에 async가 선언되어 있어야만 await이 적용된다.
├ 위의 예시는 someAsyncFunction, anotherAsyncFunction 두 함수가 비동기 코드일지라도
├ async / await이 적용되면, 항상 someAsyncFunction -> anotherAsyncFunction 순서대로 함수가 실행된다.
├ 이처럼 비동기 코드를 동기적으로 수행하게 해주는 것이 async / await 이다.
├ 이 때 async / await은 Promise 방식을 사용하기 때문에
└ someAsyncFunction과 anotherAsyncFunction 함수는 Promise를 리턴해야 한다.
async function test(){ | |
await foo(1, 2000) | |
await foo(2, 500) | |
await foo(3, 1000) | |
} | |
function foo(num, sec){ | |
return new Promise(function(resolve, reject){ | |
setTimeout( function(){ | |
console.log(num); | |
resolve("async는 Promise방식을 사용합니다."); | |
}, sec); | |
}); | |
} | |
test(); |
┌ 위의 결과는 순서대로 1, 2, 3이 나온다.
├ 이유로는 await은 Promise를 받기 때문이다.
├ 즉, 비동기 함수 foo() 실행 결과 Promise를 리턴하면 await은 이를 받아 동기적으로 수행하게 해준다.
└ 따라서, async / await은 Promise 방식을 따른다.
'React > 2022-上' 카테고리의 다른 글
Observable (0) | 2022.05.01 |
---|---|
token, XSS, CSRF (0) | 2022.04.24 |
flatten, unflatten (0) | 2022.04.24 |
Recursive Functions (0) | 2022.04.24 |
React Currying (0) | 2022.04.18 |