JavaScript_Day.10

조현기·4일 전

JavaScript_Day.10

JavaScript 마지막 복습.
콜백, Promise, async/await를 같은 문제로 다시 풀어 보며 JS 과정을 마무리한 날이다.

학습 열 번째 날이다. 오늘은 새 개념 대신 복습이었고, 핵심은 세 가지였다.

  1. 콜백 함수: 반복되는 함수를 콜백으로 합치기
  2. Promise: 상태 확인
  3. 같은 문제를 콜백, Promise, async/await로 풀기

1. 콜백 함수 복습

콜백은 다른 함수의 인자로 전달되어, 그 함수 안에서 호출되는 함수다.

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
});
호출출력
i1, 2, 3, 4, 5
i * 55, 10, 15, 20, 25
i * 1010, 20, 30, 40, 50

콜백은 비동기뿐 아니라, 이처럼 "반복되는 틀은 그대로 두고 바뀌는 동작만 바깥에서 주입한다"는 용도로도 쓰인다. Day.5에서 배운 forEach가 바로 이 구조다. forEach(콜백)은 반복문을 대신 돌아 주고, 각 요소마다 콜백을 호출한다.

위 처음 세 함수는 i < num이고 콜백 버전은 i <= num이라 반복 횟수가 하나 다르다. 같은 결과를 원하면 조건을 맞춰야 한다. 위의 콜백 버전은 5까지 출력하도록 <=로 했다.


2. Promise 상태 복습

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. 같은 문제를 세 가지 방식으로

문제는 이렇다.

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);
});

② Promise로 구현

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/await로 구현

async function fun1() {
  const num = await getNum();    // ②의 getNum() 사용
  alert(num);
}

fun1();

getNum()이 끝날 때까지 await로 기다렸다가 num에 결과를 받아 출력한다.

틀리기 쉬운 부분: Promise를 직접 만들 때의 함정

복습 중에 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로 돌려준 뒤 바깥에서 출력하는 것이 위의 ②, ③ 구조다.


4. JS 과정 돌아보기

열흘 동안 배운 흐름을 한 장으로 정리하면 이렇다.

Day주제
1DOM, 스크립트 위치, 출력문, ==/===, typeof, 변수, prompt/confirm
2parseInt, 조건문, 논리 연산자, switch, 반복문
3중첩 for, 템플릿 리터럴, 삼항 연산자, 함수, 객체, this, 화살표 함수
4변수 스코프, 함수 활용, isNaN, 배열
5String·Math·Date·Array 객체, DOM 요소 선택
6const와 객체, DOM 조작, 이벤트 3가지 방식, innerHTML
7DOM 탐색, 이벤트 객체, 폼, data- 속성, 타이머, classList, 구조 분해
8이벤트 위임, 콜백 함수, 스프레드 구문
9Promise, async/await, 이벤트 버블링, map·filter
10복습: 콜백, Promise, async/await

초반에는 값을 출력하는 코드였고, 중반부터 화면의 태그를 직접 다루기 시작했고, 후반에는 시간이 걸리는 작업을 다루는 방법(비동기)까지 왔다.


마무리

복습으로 정리한 핵심은 이렇다.

  • 콜백은 함수를 인자로 넘겨서, 받은 쪽이 원하는 시점에 호출하게 하는 방식이다. 비동기 결과를 받을 때, 그리고 반복되는 틀에서 바뀌는 동작을 주입할 때 쓴다.
  • 같은 비동기 문제를 콜백 → Promise(then) → async/await 순서로 쓰면 코드가 점점 평평하고 읽기 쉬워진다.
  • Promise를 직접 만들 때는 executor의 매개변수 (resolve, reject)를 받고, 작업이 끝나는 곳에서 반드시 resolve나 reject를 호출해야 한다.
  • await는 async 함수 안에서만 쓸 수 있다.

Tags: JavaScript 콜백함수 Promise async await 비동기 복습 개발자 학습기록

profile
난 뭘까

0개의 댓글