JavaScript 마지막 복습.
콜백, Promise, async/await를 같은 문제로 다시 풀어 보며 JS 과정을 마무리한 날이다.
학습 열 번째 날이다. 오늘은 새 개념 대신 복습이었고, 핵심은 세 가지였다.
콜백은 다른 함수의 인자로 전달되어, 그 함수 안에서 호출되는 함수다.
function fun1(name, callback) {
console.log(name);
callback();
}
const fun2 = () => {
console.log('hi');
};
fun1('홍길동', fun2);
// 홍길동
// hi
fun2를 fun2()가 아니라 이름만 넘긴다. fun1 안에서 callback()이라고 호출하는 순간 fun2가 실행된다.
필기 코드는
fun2=()=>{...}처럼const나let없이 선언했는데, 이러면 암묵적으로 전역 변수가 만들어진다. 위 코드는const를 붙였다.
콜백을 쓰는 이유는 비동기 작업 때문이다. 시간이 걸리는 작업을 기다리지 않고, "작업이 끝나면 이 함수를 실행해 줘"라고 함수를 넘겨 두는 것이다. (Day.8에서 return으로는 비동기 결과를 받을 수 없다는 것을 확인했다.)
1부터 num까지 서로 다른 계산을 출력하는 함수가 세 개 있다고 해 보자.
function test(num) {
for (let i = 1; i < num; i++) {
console.log(i);
}
}
function test2(num) {
for (let i = 1; i < num; i++) {
console.log(i * 5);
}
}
function test3(num) {
for (let i = 1; i < num; i++) {
console.log(i * 10);
}
}
세 함수는 반복문은 똑같고, 반복 안에서 하는 일만 다르다. 달라지는 부분을 콜백으로 빼면 함수 하나로 끝난다.
function func1(num, callback) {
for (let i = 1; i <= num; i++) {
callback(i); // 반복마다 i를 넘겨서 콜백 호출
}
}
func1(5, (i) => {
console.log(i); // 1 2 3 4 5
});
func1(5, (i) => {
console.log(i * 5); // 5 10 15 20 25
});
func1(5, (i) => {
console.log(i * 10); // 10 20 30 40 50
});
| 호출 | 출력 |
|---|---|
i | 1, 2, 3, 4, 5 |
i * 5 | 5, 10, 15, 20, 25 |
i * 10 | 10, 20, 30, 40, 50 |
콜백은 비동기뿐 아니라, 이처럼 "반복되는 틀은 그대로 두고 바뀌는 동작만 바깥에서 주입한다"는 용도로도 쓰인다. Day.5에서 배운 forEach가 바로 이 구조다. forEach(콜백)은 반복문을 대신 돌아 주고, 각 요소마다 콜백을 호출한다.
위 처음 세 함수는
i < num이고 콜백 버전은i <= num이라 반복 횟수가 하나 다르다. 같은 결과를 원하면 조건을 맞춰야 한다. 위의 콜백 버전은 5까지 출력하도록<=로 했다.
const promise = new Promise((resolve, reject) => {
// 실제로 비동기 작업을 실행하는 함수 (executor)
setTimeout(() => {
const n = 100;
if (typeof n === 'number') {
resolve(n + 100); // resolve(200)
} else {
reject('너 숫자 아닌데');
}
}, 3000); // 3초 후에 promise가 resolve(200)으로 완료
});
// 4초 후에 콘솔에 출력
setTimeout(() => {
console.log(promise); // Promise {<fulfilled>: 200}
}, 4000);
3초에 resolve(200)이 호출되어 pending에서 fulfilled로 바뀌고, 4초에 출력하므로 fulfilled: 200이 보인다. 출력 시점을 2초로 앞당기면 같은 코드가 Promise {<pending>}으로 나온다.
문제는 이렇다.
3초 후에 랜덤하게 0~9 사이의 숫자를 반환하는 함수
getNum()을 만들어라. 그 숫자를 받아서alert()으로 출력해라.
function getNum(callback) {
setTimeout(() => {
let n = Math.floor(Math.random() * 10); // 0~9
callback(n); // 3초 뒤 n을 콜백에 전달
}, 3000);
}
getNum((n) => {
alert(n);
});
function getNum() {
return new Promise((resolve) => {
setTimeout(() => {
let n = Math.floor(Math.random() * 10);
resolve(n); // 상태를 fulfilled로 바꾸고 n을 전달
}, 3000);
});
}
getNum().then((n) => {
alert(n);
});
getNum은 Promise 객체를 반환하는 함수다. 3초 동안 대기하다가, 3초가 지나면 0~9 사이의 랜덤 정수를 만들고, resolve(n)으로 상태를 fulfilled로 바꾸면서 값 n을 전달한다.
async function fun1() {
const num = await getNum(); // ②의 getNum() 사용
alert(num);
}
fun1();
getNum()이 끝날 때까지 await로 기다렸다가 num에 결과를 받아 출력한다.
복습 중에 async/await로 한 번에 풀어 보려고 이렇게 써 본 코드가 있었다.
async function test() {
await new Promise(() => { // ← 매개변수가 없다
setTimeout(() => {
let num = parseInt(Math.random() * 10);
alert(num);
resolve(); // ← resolve는 어디서 왔나?
}, 3000);
});
}
test();
이 코드는 숫자 알림은 뜨지만, 곧이어 resolve is not defined라는 오류가 난다. 원인은 두 가지다.
new Promise(() => {...})처럼 executor가 resolve, reject를 매개변수로 받지 않았다. 그래서 안에서 resolve()를 호출하려 해도 그런 이름의 함수가 없다.resolve가 호출되지 않은 Promise는 영원히 pending이다. 그러면 await가 끝나지 않는다.Day.9에서 정리한 대로, executor는 (resolve, reject) => { ... } 형태로 써야 하고, 작업이 끝나는 지점에서 resolve(값)을 호출해야 await가 풀린다. 그리고 이 문제는 "받은 숫자를 alert로 출력"이므로, Promise 안에서 alert까지 하지 않고 값을 resolve로 돌려준 뒤 바깥에서 출력하는 것이 위의 ②, ③ 구조다.
열흘 동안 배운 흐름을 한 장으로 정리하면 이렇다.
| Day | 주제 |
|---|---|
| 1 | DOM, 스크립트 위치, 출력문, ==/===, typeof, 변수, prompt/confirm |
| 2 | parseInt, 조건문, 논리 연산자, switch, 반복문 |
| 3 | 중첩 for, 템플릿 리터럴, 삼항 연산자, 함수, 객체, this, 화살표 함수 |
| 4 | 변수 스코프, 함수 활용, isNaN, 배열 |
| 5 | String·Math·Date·Array 객체, DOM 요소 선택 |
| 6 | const와 객체, DOM 조작, 이벤트 3가지 방식, innerHTML |
| 7 | DOM 탐색, 이벤트 객체, 폼, data- 속성, 타이머, classList, 구조 분해 |
| 8 | 이벤트 위임, 콜백 함수, 스프레드 구문 |
| 9 | Promise, async/await, 이벤트 버블링, map·filter |
| 10 | 복습: 콜백, Promise, async/await |
초반에는 값을 출력하는 코드였고, 중반부터 화면의 태그를 직접 다루기 시작했고, 후반에는 시간이 걸리는 작업을 다루는 방법(비동기)까지 왔다.
복습으로 정리한 핵심은 이렇다.
then) → async/await 순서로 쓰면 코드가 점점 평평하고 읽기 쉬워진다.(resolve, reject)를 받고, 작업이 끝나는 곳에서 반드시 resolve나 reject를 호출해야 한다.await는 async 함수 안에서만 쓸 수 있다.Tags: JavaScript 콜백함수 Promise async await 비동기 복습 개발자 학습기록