나중에 하기: setTimeout에서는 일정 시간 뒤에 함수를 불렀습니다. 그런데 서버에서 메뉴 가격을 받아 오거나 파일을 읽는 일은 끝난 뒤에 결과 값을 돌려받아 다음 계산에 써야 합니다. setTimeout만 쓰면 콜백 함수 안에 또 콜백 함수를 겹겹이 넣어야 해서 코드가 오른쪽으로 깊어집니다. 자바스크립트는 '지금은 없지만 나중에 도착할 값'을 담는 상자인 Promise 객체로 이 문제를 풉니다.
Promise의 세 상태와 then
Promise 객체는 처음 만들어질 때 비어 있는 대기(pending) 상태로 시작해서, 일이 끝나면 다음 두 상태 가운데 하나로 결정됩니다.
- 대기(
pending): 아직 결과가 오지 않아 기다리는 상태입니다. - 이행(
fulfilled): 일이 성공해 결과 값이 상자 안에 담긴 상태입니다(resolve(값)호출). - 거부(
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> }
도착: 40006번 줄의 console.log(p)를 실행할 때는 아직 50밀리초가 지나지 않았으므로 숫자 4000이 아니라 대기 중인 상자 Promise { <pending> }이 찍힙니다. 50밀리초 뒤 resolve(4000)이 불리면 상자가 이행 상태로 바뀌고, p.then(...)에 등록한 함수가 4000을 받아 도착: 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);
});3500return 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);
});라테 주문