콜백 패턴에 대해 알기 전에 동기와 비동기에 대해 짚고 넘어가자.
동기 | 비동기 |
순차적으로 실행 한 작업이 완료 되어야 다음 작업 시작 장점: 프로그램의 흐름을 이해하고 예측하기 쉬움 단점: 작업이 끝나기를 기다리는 동안 다른 작업을 수행할 수 없어 효율성 떨어짐 |
각각 독립적으로 실행 완료를 기다리지 않고 다른 작업 시작 장점: 효율적으로 작동하며 여러 작업을 동시에 처리 단점: 프로그램의 흐름을 추적하고 관리가 복잡함 |
비동기 코드가 어려운 이유
- 일반적인 js코드 작성 방식은 동기식 (자바스크립트는 위에서 아래로 순차적으로 코드를 실행하기 때문)
- 비동기는 내부적으로 비동기 메커니즘 ( 예: 이벤트루프 )을 통해서 도익와는 다른 방식으로 처리
- 개발자는 코드를 동기적으로 작성하지만, 비동기 코드는 시스템의 내부 처리 프로세스 (이벤트 루프)에 의해 비동기식으로 처리가 되는 데서 오는 혼란
- 이러한 복잡성 때문에 비동기 코드를 동기 코드처럼 보이게 발전을 거듭하는 중 ( async, await )
비동기 코드 종류들
HTTP 요청 처리 XMLHttpRequest (XHR) FetchAPI |
비동기 프로그래밍 패턴 Callback,Promise Async/Await |
타이머 및 애니메이션 setTimeout requestAnimationFrame |
백그라운드 작업 Web Workers Service Workers |
Callback?
함수는 또다른 함수를 인자로 전달 받을 수 있다. 여기서 다른 함수의 인자로 전달되는 함수를 콜백함수라 한다.
콜백함수는 콜백함수를 전달받은 함수에 의해서 호출 된다.
쉽게 말해 나중에 호출할 함수를 의미한다.
function main (x) {
console.log(x);
}
main ();
여기 main이라는 이름의 간단한 함수를 만들었다.
이 main 함수의 인자에 숫자를 전달할 수 있고 문자열을 전달할 수 있다.( main(1); main('asdf'); )
이것말고도 함수를 인자로 전달할 수 있는데 main에 전달될 함수가 바로 콜백함수.
function main (x) {
console.log(x);
}
function sayHi() {
console.log("안녕하세요");
}
main (sayHi);
sayHi라는 함수를 main의 인자로 전달해줬으니 sayHi가 콜백함수가 되는 것이다.
console에 찍힌걸 보면
함수가 정상적으로 출력이 된다.
function main (x) {
x();
}
function sayHi() {
console.log("안녕하세요");
}
main (sayHi);
main의 인자로 전달받는 것이 함수이기 때문에 함수를 바로 실행시킬 수 있게 x(); 라고 수정하면
console에 함수 자체가 아닌 안녕 이라는 텍스트를 출력시킬 수 있다.
sayHi 콜백함수는 main에 전달되었고 이 콜백함수가 언제 어떻게 실행될지는 main 함수의 구현 사항에 달려있게된다.
function main (x) {
console.log('준비작업..');
x();
console.log('정리작업..');
}
function sayHi() {
console.log("안녕하세요");
}
main (sayHi);
이렇게 콜백함수 앞뒤로 console.log를 넣어주면
이런식으로 main의 구현 사항에 따라 나타나게 된다.
sayHi라는 함수가 main함수에 의해서 언제 실행될지는 모르지만 어찌됐든 나중에 불러오게 된다해서 콜백함수라 이름이 붙은 것 같다.
function main (x) {
console.log('준비작업..');
x();
console.log('정리작업..');
}
main (function () {
console.log("안녕하세요");
});
/* main (() => {
console.log("안녕하세요");
});
*/
또한 콜백함수를 외부에서 정의할 수도 있지만 위 예시처럼 인자로 전달하는 부분에 함수 그대로 넣어주는 것도 가능하다.
이러한 경우에는 외부에서 가져오는 것이 아니고 main의 인자에 바로 넣어주는거라 이름이 필요 없으니 익명함수로 전달 해준다. ( 주석처럼 화살표 함수로 간단하게 표현 가능)
여기서 실수할 수 있는 부분!
function main (x) {
console.log('준비작업..');
x();
console.log('정리작업..');
}
function sayHi() {
console.log("안녕하세요");
}
main (sayHi());
마지막 부분에 함수를 호출하겠다고 소괄호 ()를 붙여버리면 콜백함수가 되는게 아닌 sayHi 함수의 리턴값을 인자로 전달하는게 되어버려서 즉각 실행시키게 된다. (return 1 이런식으로 리턴값이 있는 경우에는 해당값 반환, 위 예시처럼 return이 없는 경우에는 undefined 반환)
CallBack 함수 활용해보기
콜백함수를 사용하여 언어에 따른 인사를 출력하도록 해볼 것이다.
function greetToUser(greet) {
const name = '윤구';
greet(name);
}
function greetInKorean(name) {
console.log(name + '님, 안녕하세요.');
}
function greetInEnglish(name) {
console.log('Hi, ' + name);
}
greetToUser(greetInKorean); // 윤구님, 안녕하세요.
greetToUser(greetInEnglish); // Hi, 윤구
1. greetInKorean 이라는 콜백함수를 만들어주었고 name이라는 인자를 받는다.
2. 이를 greetToUser의 인자인 greet에 넣어준다.
3. greetToUser 내부에는 name이 정의되어있고 콜백함수 인자에 그 값이 들어가게 되며 console.log가 실행된다.
콜백함수를 잘 나타내는 대표적인 예시 setTimeout
setTimeout(() => {
console.log('Hi');
}, 1000); // 콘솔창에 1초뒤 Hi가 찍히게 된다.
setTimeout() 안에 Hi를 출력시키는 익명함수이자 콜백함수가 첫번째 인자로 들어가 있고, 두번째 인자에는 ms단위로 몇 초 후에 콜백함수를 실행시킬 건지 정해준다.
이 콜백함수는 setTimeout에 의해 호출이 되기 때문에 내부 로직에 의해 1초 뒤 Hi를 출력시킨다.
function findUser(id) {
let user;
setTimeout(function () {
console.log("waited 0.1 sec.");
user = {
id: id,
name: "User" + id,
email: id + "@test.com",
};
}, 100);
return user;
}
const user = findUser(1);
console.log("user:", user);
/*
undefined
waited 0.1 sec.
*/
이번에는 조금 더 복잡하게 setTimeout을 사용하여 0.1초 후에 user 변수에 객체를 할당하고, setTimeout이 실행될때 콘솔로 몇초가 걸리는지 출력한다.
기대하는 작업 순서는
findUser에 인자를 1로 받음 - setTimeout의 console.log출력 - 인자를 받은 user 객체가 콘솔에 출력
하지만 정작 순서는 undefined 후 setTimeout의 console.log출력
비동기적 처리에 의해 setTimeout이 진행되는 동안 그 다음 코드를 실행시켜버리면서 값이 아직 들어오지 않아 undefined를 먼저 출력, setTimeout이 끝나서 콘솔 출력.
콜백함수를 사용하여 수정하면
function findUserAndCallBack(id, cb) {
setTimeout(function () {
console.log("waited 0.1 sec.");
const user = {
id: id,
name: "User" + id,
email: id + "@test.com",
};
cb(user);
}, 100);
}
findUserAndCallBack(1, function (user) {
console.log("user:", user);
});
/*
waited 0.1 sec.
user: {id: 1, name: "user1", email: "1@test.com"}
*/
두번째 인자 cb 콜백함수에 실행될 로직을 넘겨주고 setTimeout은 0.1초 후에 이 콜백 함수를 호출하게 된다.
마무리로 정리하자면,
- 다른 함수의 인자에 들어가는 함수가 콜백 함수.
- 콜백 함수는 전달받은 함수(부모컴포넌트같은 느낌)의 구현 사항에 따라 언제 어떻게 실행될지 결정
- 비동기 처리에서 작업의 순서를 맞춰야할 때 사용하는 것이 콜백함수.
1. 사용자 이벤트 처리
2. 네트워크 응답 처리
3. 파일을 읽고 쓰는 등의 파일 시스템 작업
4. 의도적으로 시간을 지연하기 위한 작업
ex) 알람 설정
위와 같은 상황에서 비동기 처리 하지 않는다면 사용자는 멈춰있는 화면을 보게 된다.
하지만 비동기 처리를 하게 되면 예상치 못한 순서로 실행될 수 있고 이를 해결하기 위해 콜백함수를 사용.
=> 비동기처리를 하면 값을 받기전 다른 위치의 코드가 먼저 실행될 수 있고 undefined가 나타날 수 있음.
그래서 비동기 함수를 호출할 때 결과값을 리턴 받으려 하지 말고 결과 값을 통해 처리할 로직을 콜백 함수로 넘기는 식으로 사용해야 에상된 결과를 얻을 수 있고 콜백함수를 사용하는 이유가 됨.
최대한 3줄 요약으로 마무리 정리 하려고 노력하는데 실패.
콜백에 대해서 공부하고 느낀점
처음 동기 비동기를 공부할때 단순히 직렬적 처리 방식과 병렬적 처리 방식으로만 생각했다.
맞는 부분도 있지만 내가 생각했던 병렬적 처리 방식은 동시다발적으로 처리가 이루어지는 것만 생각하며 접근하니 콜백에 대해서 이해가 쉽게 되지 않았다. (쉬운 내용도 아니지만..)
비동기적으로 처리하는거면 콜백함수를 사용했을때 로딩이 느린 부분은 그거대로 로딩시키면서 콜백함수 부분이 동시에 실행시키는거 아닌가?? << 실제로 이렇게 생각했었다.
맞는 부분도 있으면서 틀린 부분도 있는게 자바스크립트의 동작 원리를 정확하게 알지 못해 저런식으로 생각하게 된거 같다. 동작원리나 CS 부분, 동기와 비동기도 제대로 공부해야겠다는 생각이 들었다.