본문으로 건너뛰기

Promise: 나중에 올 값

자바스크립트 목차비동기레슨 59 / 76

목차

나중에 하기: setTimeout에서는 일정 시간 뒤에 함수를 불렀습니다. 그런데 서버에서 메뉴 가격을 받아 오거나 파일을 읽는 일은 끝난 뒤에 결과 값을 돌려받아 다음 계산에 써야 합니다. setTimeout만 쓰면 콜백 함수 안에 또 콜백 함수를 겹겹이 넣어야 해서 코드가 오른쪽으로 깊어집니다. 자바스크립트는 '지금은 없지만 나중에 도착할 값'을 담는 상자인 Promise 객체로 이 문제를 풉니다.

Promise의 세 상태와 then

Promise 객체는 처음 만들어질 때 비어 있는 대기(pending) 상태로 시작해서, 일이 끝나면 다음 두 상태 가운데 하나로 결정됩니다.

  1. 대기(pending): 아직 결과가 오지 않아 기다리는 상태입니다.
  2. 이행(fulfilled): 일이 성공해 결과 값이 상자 안에 담긴 상태입니다(resolve(값) 호출).
  3. 거부(rejected): 일이 실패해 오류 이유가 상자 안에 담긴 상태입니다(reject(오류) 호출).

Promise 상자 p에 나중에 값이 담겼을 때 할 일은 p.then((값) => { ... })⁠으로 등록합니다.

const p = new Promise(
  (resolve) => {
    setTimeout(() => {
      resolve(4000);
    }, 50);
  },
);
console.log(p);
p.then((price) => {
  console.log('도착:', price);
});
실행 결과
Promise { <pending> }
도착: 4000

6번 줄의 console.log(p)를 실행할 때는 아직 50밀리초가 지나지 않았으므로 숫자 4000이 아니라 대기 중인 상자 Promise { <pending> }⁠이 찍힙니다. 50밀리초 뒤 resolve(4000)이 불리면 상자가 이행 상태로 바뀌고, p.then(...)에 등록한 함수가 4000을 받아 도착: 4000을 찍습니다.

5단계 중 1단계. 1~3번 줄의 `new Promise(...)`가 실행되면서 아직 결과가 정해지지 않은 `p`(`대기 / pending`)를 만들고, 100ms 뒤에 `resolve(4000)`을 부를 타이머를 걸어 둡니다.

이미 값이 담긴 Promise와 then 이어 붙이기

이미 알고 있는 값을 곧바로 이행 상태의 Promise 상자에 담아 만들 때는 Promise.resolve(값)을 씁니다.

const p = Promise.resolve(4500);
console.log(p);
p.then((price) => {
  console.log('가격:', price);
});
실행 결과
Promise { 4500 }
가격: 4500

또한 .then()에 넘긴 함수가 return으로 값을 돌려주면, 그 값을 담은 **새 Promise**가 만들어져 바로 뒤에 .then()을 사슬처럼 이어 붙일 수 있습니다.

Promise.resolve(4000)
  .then((price) => price + 500)
  .then((total) => {
    console.log('합계:', total);
  });
실행 결과
합계: 4500

첫 번째 .then이 4000 + 500인 4500을 돌려주었고, 두 번째 .then이 그 4500을 total로 받아 찍었습니다. 콜백 안에 콜백을 겹겹이 들여쓰던 옛 방식 대신 .then을 아래로 나란히 이어 쓸 수 있습니다.

자주 틀리는 곳

1. new Promise에 넘긴 함수도 나중에 돈다고 생각함

p.then(...)에 넘긴 함수는 나중에 돌지만, new Promise(...) 생성자에 넘긴 함수는 상자를 만드는 그 자리에서 지금 바로 실행됩니다.

const p = new Promise(
  (resolve) => {
    console.log('1. 안쪽');
    resolve('완료');
  },
);
p.then((v) => {
  console.log('3. then:', v);
});
console.log('2. 바깥 끝');
실행 결과
1. 안쪽
2. 바깥 끝
3. then: 완료

new Promise 안의 '1. 안쪽'이 가장 먼저 찍히고, 바깥의 '2. 바깥 끝'이 찍힌 뒤에야 .then에 넘긴 '3. then: 완료'가 찍힙니다.

2. .then 안에서 return을 빠뜨림

.then을 이어 붙일 때 앞 .then의 함수가 중괄호 { }를 쓰고 return을 빠뜨리면, 함수가 undefined를 돌려주어 다음 .then이 undefined를 받게 됩니다.

Promise.resolve(10)
  .then((n) => {
    n * 2;
  })
  .then((res) => {
    console.log('결과:', res);
  });
실행 결과
결과: undefined

다음 .then으로 값을 넘기려면 return n * 2;처럼 return을 적거나 중괄호 없는 화살표 함수 (n) => n * 2로 써야 합니다.

요약

  • Promise: 나중에 올 값을 담는 상자로, 대기(pending)에서 시작해 이행(fulfilled) 또는 거부(rejected)로 바뀐다.
  • p.then((값) => ...): Promise가 이행된 뒤에 결과 값을 받아 실행할 함수를 등록한다.
  • .then 이어 붙이기: 앞 .then에서 return한 값이 다음 .then의 매개변수로 넘어간다.
  • new Promise에 넘긴 함수는 즉시 실행되고, .then에 넘긴 함수만 지금 도는 코드가 끝난 뒤 나중에 실행된다.

연습 문제

문제 1

다음 코드를 실행했을 때 출력되는 세 줄을 순서대로 쓰세요.

Promise.resolve(2)
  .then((n) => n * 3)
  .then((n) => n + 1)
  .then((n) => {
    console.log('최종:', n);
  });
console.log('먼저');
힌트

.then에 등록한 함수들은 지금 도는 코드(console.log('먼저'))가 끝난 뒤에 차례로 실행됩니다.

정답
실행 결과
먼저
최종: 7
  • 지금 도는 코드인 7번 줄의 '먼저'가 첫 줄에 찍힙니다.
  • 이어서 .then 사슬이 2 → 6 → 7을 차례로 넘겨 최종: 7을 찍습니다.

문제 2

다음 코드는 3000에 500을 더해 3500을 찍으려 했지만 undefined가 찍혔습니다. 3번 줄 한 줄만 고쳐 3500이 찍히게 만드세요.

Promise.resolve(3000)
  .then((p) => {
    p + 500;
  })
  .then((total) => {
    console.log(total);
  });
힌트

중괄호 { }가 있는 화살표 함수에서 다음 .then으로 값을 넘기려면 return이 있어야 합니다.

정답
  • 고친 줄: return p + 500;
Promise.resolve(3000)
  .then((p) => {
    return p + 500;
  })
  .then((total) => {
    console.log(total);
  });
실행 결과
3500
  • return p + 500;으로 값을 돌려주어야 다음 .then의 total에 3500이 넘어갑니다.

문제 3

Promise.resolve('라테')⁠로 시작해 첫 번째 .then에서 뒤에 ' 주문'을 붙인 문자열을 돌려주고, 두 번째 .then에서 그 문자열을 출력하는 코드를 작성하세요.

라테 주문
// 여기에 Promise.resolve('라테')와
// 두 단계의 .then을 이어 쓰세요
힌트

.then((s) => s + ' 주문') 뒤에 두 번째 .then을 이어 붙여 출력합니다.

정답
Promise.resolve('라테')
  .then((s) => s + ' 주문')
  .then((msg) => {
    console.log(msg);
  });
실행 결과
라테 주문

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

자바스크립트 목차로