[JS] 코어자바스크립트 - 콜백함수
코어 자바스크립트를 꼼꼼하게 정독하고
복습하며 정리한 내용을 토대로 F-lab 멘토링을 받고있다.
이 글은 멘토링으로 알게된 것들을 다시한번 정리해보는 글이다.
콜백함수란 다른 코드의 인자로 넘겨주는 함수이다.
콜백함수를 넘겨받은 코드는 특정조건을 만족하면 콜백함수를 실행하는 제어권을 갖는다.
제어권을 갖는다는 것
호출 시점
콜백함수의 제어권을 넘겨받은 코드는 콜백함수 호출 시점에 대한 제어권을 가진다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let count = 0;
const printCount = () => {
console.log(count);
count += 1;
if (count > 4) clearInterval(timer);
};
// printCount함수는 count를 출력하고, 1씩 더한다
// 만약 countr가 4보다 크면 timer에 저장된 반복실행값을 종료한다.
const timer = setInterval(printCount, 300);
// 300ms 마다 printCount함수를 실행한다.
// setInterval은 반복실행시 내용을 특정할 수 있는 ID값을 반환하므로
// 그것을 변수로 저장해 원하는 시점에 종료할 수 있도록 했다.
콜백함수의 인자
특정 콜백함수를 사용할때는 인자의 순서가 중요하다.
이낮로 쓴 각 단어들은 의미가 없고, 컴퓨터는 그저 순서에 의해서만 각 인자를 구분한다.
따라서 특정 메서드를 호출할때 정의된 규칙에 따라 함수를 작성해야 한다.
1
2
3
4
5
6
7
8
const nums = [10, 20, 30].map((index, value) => {
return value + 5;
});
console.log(nums); // [5, 6, 7]
// 여기서 map 메서드의 인자로 들어가는 value와 index의 위치를 바꾸고싶어서
// map((index, value)로 선언한다해도
// map 메서드는 첫번째 인자를 value, 두번째 인자를 index로 인식한다.
콜백함수 내부의 this
콜백함수는 함수다
콜백함수는 결국 함수이다.
콜백함수로 어떤 객체의 메서드를 전달하더라도, 그 메서드는 함수로서 사용된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
const nums = {
vals: [1, 2, 3],
method() {
console.log(this);
},
};
nums.method();
// nums 객체의 메소드로 호출하여 this는 nums 객체를 참조한다.
[4, 5, 6].forEach(nums.method);
// 배열에 대한 forEach의 콜백함수로 nums객체의 메소드를 호출했지만
// 결국 함수로서 실행되어 this는 전역객체를 참조한다.
// 콜백함수에서 this를 지정하는 인자를 설정하지 않으면 this는 전역객체를 참조한다.
this
콜백함수도 함수이기때문에 기본적으로 this는 전역객체를 참조한다.
하지만 제어권을 넘겨받을 코드에서 this가 될 대상을 지정한 경우 그 대상을 참조한다.
1
2
3
4
5
6
document.body.innerHTML += '<button id="a">Hello</button>';
document.body.querySelector("#a").addEventListener("click", function (e) {
console.log(this, e);
});
// addEventListener는 내부에서 콜백함수를 호출 할 때
// addEventListener를 호출한 주체인 HTML엘리먼트를 this로 넘기도록 정의되어 있다.
this에 다른값 바인딩
별도의 인자로 this값을 받는 함수의 경우는 원하는 값을 넘겨주면 되지만
그렇지 않은 경우에 this는 전역객체를 참조한다.
그래서 필요한 경우 ES5에서 추가된 bind 메서드를 이용해 this를 정해줄 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const jessie = {
name: "jessie",
printName() {
console.log(this.name);
},
};
const tom = { name: "tom" };
setTimeout(jessie.printName, 1000);
// 메소드를 콜백함수자리에 넣었지만 함수로 실행되어 this는 전역객체를 바라본다.
// 전역공간에 선언된 name이 없으므로 this는 undefined
setTimeout(jessie.printName.bind(jessie), 2000);
// 원래 this는 전역객체를 참조하지만 bind메서드로 jessie객체를 할당했으므로
// this.name은 jessie
setTimeout(jessie.printName.bind(tom), 3000);
// this.name은 tom
콜백과 비동기 제어
동기와 비동기
동기적
현재 실행중인 코드가 완료된 후에 다음 코드를 실행하는 방식
비동기적
현재 실행중인 코드가 요청에 의해 특정 시간이나 조건에 맞을때까지 함수의 실행을 보류하고
다음코드를 먼저 실행시키는 방식이며, 기다리던 응답이 왔을때 비로소 함수를 실행시킨다.
콜백지옥
콜백지옥은 콜백함수를 익명함수로 전달하는 과정이 반복되어
코드의 들여쓰기 수준이 감당하기 힘들정도로 깊어지는 현상이다.
비동기적 작업 수행을 위해 이런 형태가 발생하게 되는데,
가독성도 떨어지며 오류부분을 찾거나 수정하기도 어려워진다.
지금은 웹의 복잡도가 높아진만큼 비동기적 코드의 비중이 많아져 콜백지옥에 빠지기 쉽다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
setTimeout(
function (name) {
let coffeeList = ` ${name}`;
console.log(coffeeList);
setTimeout(
function (name) {
coffeeList += ` ${name}`;
console.log(coffeeList);
setTimeout(
function (name) {
coffeeList += ` ${name}`;
console.log(coffeeList);
},
500,
"Latte"
);
},
500,
"Americano"
);
},
500,
"Espresso"
);
// 0.5초마다 커피 목록을 수집해서 출력한다.
// 전달 순서가 아래에서 위로 향하고있어 어색하게 느껴진다.
익명함수를 모두 기명함수로 전환해도 되겠지만,
일회성 함수를 전부 변수에 할당하는 것도 좋은 방법은 아닐 수 있다.
그래서 등장한 Promise
비동기적 작업을 하는 코드의 가독성을 높이기 위해서 Promise가 도입되었다.
Promise를 사용하면 비동기 작업에서 마치 동기작업을 진행한것 처럼 값을 반환할 수 있다.
Promise의 인자로 넘겨주는 콜백함수는 호출시 바로 실행되지만
내부에서 resolve(성공시 실행) 또는 reject(실패시 실행) 함수가 호출된다면
그것들이 실행되기 전까지는 다음구문으로 넘어가지 않는다.
(then : 성공시 다음 함수 실행 / catch : 실패 시 에러 캐치)
1
2
3
4
5
6
7
8
9
10
11
12
13
const addCoffee = function (name) {
return function (prevName) {
return new Promise(function (resolve) {
setTimeout(function () {
const newName = prevName ? (prevName += ` ${name}`) : name;
console.log(newName);
resolve(newName);
}, 500);
});
};
};
addCoffee("Espresso")().then(addCoffee("Americano")).then(addCoffee("Latte"));
// 위 코드를 Promise를 활용해 이렇게 바꿔볼 수 있다.
async / await
비동기 작업을 수행하고자 하는 함수 앞에 async를 표기하고
내부에서 비동기 작업이 실행되는 코드 앞에 await를 표기하면
더 깔끔하게 Promise를 생성할 수 있다.
await를 사용하기 위해서는 함수 앞에 async가 있어야한다.
await는 뒤의 함수가 완료되기 전까지 다음 코드가 실행되지 않고 대기하게 해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const addCoffee = function (name) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(name);
}, 500);
});
};
const coffeeMaker = async function () {
let coffeeList = "";
const _addCoffee = async function (name) {
coffeeList += (coffeeList ? " " : "") + (await addCoffee(name));
};
await _addCoffee("Espresso");
console.log(coffeeList);
await _addCoffee("Americano");
console.log(coffeeList);
await _addCoffee("Latte");
console.log(coffeeList);
};
coffeeMaker();
Leave a comment