해결된 질문
작성
·
29
0
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
promise
.then((value) => {
console.log(`${value} 번째 Promise 의 then`);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 1000);
});
})
.then((value) => {
console.log(`${value} 번째 Promise 의 then`);
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('강제 에러'));
}, 1000);
});
})
.catch((error) => {
console.log(error);
});
이런 형태로 then 과 catch 를 메서드 체이닝할 수 있는 이유는 then 과 catch 메서드가 Promise 를 반환하기 때문이라고 이해를 했습니다. 그런데 then() 메서드에 전달하는 콜백 함수에서 return new Promise()
를 하여 Promise 를 반환하면 해당 Promise 가 반환된다고 이해를 했는데 어떻게 그것이 가능한 지 잘 이해가 안됩니다. then 은 동기적으로 실행되고 then 메서드에 전달된 콜백은 마이크로 태스크 큐에 등록된 후에 콜스택이 모두 비어졌을 때 호출된다고 이해를 했는데... 연결고리가 이어지지 않는 느낌입니다.
답변 2
0
안녕하세요. Droid 님 질문 주셔서 감사합니다! 😊
then() 메서드의 반환값을 좀 더 자세히 살펴보겠습니다.
new Promise((resolve, reject) => {
resolve(10);
})
하면 결과로 fulfilled 상태의 Promise 가 반환되는 것을 아실 것입니다.
Promise {<fulfilled>: undefined}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
해당 내용은 아래 강좌에서 다루었습니다.
강좌에서는 then() 메서드에서 Promise 를 반환하는 경우만 다루었는데요. 아래와 같이 일반 값을 반환하면 어떻게 될까요?
new Promise((resolve, reject) => {
resolve(10);
})
.then((value) => {
return value * 2;
})
이 때에도 fulfilled 상태의 Promise 가 반환됩니다.
Promise {<fulfilled>: 20}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 20
Promise 가 아닌 값을 반환했는데 Promise 가 반환됩니다. 이 것은 then() 메서드에서 일반 값을 반환할 경우, 자동으로 Promise.resolve 로 한 번 감싸기 때문입니다. 즉, 아래와 같은 것이죠.
new Promise((resolve, reject) => {
resolve(10);
})
.then((value) => {
return Promise.resolve(value * 2);
})
그래서 아래와 같이 계속 then() 메서드를 체이닝해서 사용할 수 있습니다.
new Promise((resolve, reject) => {
resolve(10);
})
.then((value) => {
return Promise.resolve(value * 2);
})
.then((value) => console.log(`값이 도착했습니다. ${value}`))
아무것도 반환하지 않을 경우는 어떨까요? 자바스크립트에서 함수가 아무 것도 반환하지 않더라도 undefined 를 반환합니다.
function a() {}
a() // undefined 를 반환합니다.
그래서 아래와 같이 then 메서드에서 아무 것도 반환하지 않더라도
new Promise((resolve, reject) => {
resolve(10);
})
.then((value) => {
console.log(value);
})
undefined 가 반환되고 then 에서 반환된 값은 자동으로 Promise.resolve() 로 감싸집니다.
new Promise((resolve, reject) => {
resolve(10);
})
.then((value) => {
console.log(value);
return Promise.resolve();
})
위 코드처럼 실행됩니다. 그래서 반환값으로 undefined 를 PromiseResult 로 갖는 fulfilled 상태의 Promise 가 반환되는 것입니다.
Promise {<fulfilled>: undefined}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
만약 일반 값이 아닌 Error 를 throw 하면 어떻게 될까요?
new Promise((resolve, reject) => {
resolve(10);
})
.then((value) => {
throw new Error('오류가 발생했습니다');
})
이 때는 상태가 'rejected' 인 Promise 가 반환됩니다.
Promise {<rejected>: Error: 오류가 발생했습니다.}
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: Error: 오류가 발생했습니다.
즉, 일반값을 반환하거나 오류를 throw 하면 자동으로 Promise 로 감싸지는 것을 알 수 있습니다. 그렇기 때문에 Promise 의 메서드인 then 또는 catch 를 체이닝을 통해서 사용할 수 있는 것입니다.
자 그럼 새로운 Promise 를 반환할 때는 언제인가요? 새로운 비동기 작업이 필요할 때입니다. 그런데 이 때, 주의 깊게 생각해 볼 문제가 있습니다.
만약 Promise 를 반환하는데 일반값이나 오류 throw 일 때처럼 Promise 로 감싸면 어떻게 될까요?
new Promise((resolve, reject) => {
resolve(10);
})
.then((value) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value * 2), 1000);
});
})
만약 then 메서드가 반환하는 Promise 를 다시 Promise 로 감싼다면, Promise<Promise<Value, Error>, Error>
처럼 될 것입니다. 즉, 배열의 배열처럼 [array, [of, [array]]]
Promise 가 계속 쌓이게 되는 것입니다.
그렇게 되면 다음 then() 에서 아래와 같이 사용해야 할 것 입니다.
new Promise((resolve, reject) => {
resolve(10);
})
.then((value) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value * 2), 1000);
});
})
.then((if_promise) => {
if_promise.then(value => {
console.log(value);
})
})
하지만 뭔가 이상하지 않나요? 네 그렇습니다. 만약 Promise 를 위처럼 사용해야 한다면 '콜백헬'에서 발생했던, 깊이가 깊어지는 문제가 다시 발생합니다.
그래서 Promise 는 [array, [of, [array]]]
의 flat() 메서드처럼 중첩된 Promise 를 flat 하게 펴주는 기능을 자동으로 제공합니다.
['array', ['of', ['array']]].flat(Infinity)
// 출력결과
['array', 'of', 'array']
그래서 아래와 같이 then() 메서드에서 Promise 를 반환하면,
new Promise((resolve, reject) => {
resolve(10);
})
.then((value) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value * 2), 1000);
});
})
Promise<Promise<Value, Error>, Error>
가 flat 되어 Promise<Value, Error>
가 반환됩니다. 그래서 결과가 아래와 같죠.
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 20
PromiseResult 로 값은 20이 있지만, Promise 가 pending 상태에 있기 때문에, then 을 통해서 값을 얻어야 합니다.
new Promise((resolve, reject) => {
resolve(10);
})
.then((value) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value * 2), 1000);
});
})
.then((value) => console.log(value));
그러면, 1초 뒤에 콘솔에 '20' 이 출력됩니다. 따라서 정리해 보면, Promise 는 then 메서드나 catch 메서드에서 일반값을 반환하거나 Error 를 throw 하면 Promise 로 자동으로 감싸서 반환합니다. 그래서 then 이나 catch 를 사용해서 계속 체이닝을 이어갈 수 있습니다.
그리고 then 메서드나 catch 메서드에서 Promise 를 반환하면 자동으로 flat 을 실행해 중첩이 없는 Promise 를 반환합니다. 그래서 콜백헬에서 나타나는 깊이가 깊어지는 문제를 해결합니다.
여기에서 잠깐 나온 Promise 를 반환하면 자동으로 flat 을 실행해 중첩이 없는 Promise 를 반환하는 내용은 모나드의 map 과 flatMap 과 비슷합니다. 즉, Promise 의 then 이 map 연산자와 비슷하면서도 반환값에 대해서는 flatMap 기능을 수행해 주는 것과 비슷합니다.
참고로, 모나드에 대한 내용은 Result 타입과 모나드 강좌에서 다루고 있습니다.
답변이 도움 되었기를 바랍니다. 감사합니다.
0
안녕하세요, 인프런 AI 인턴입니다. Droid님께서 질문하신 Promise 체이닝과 관련하여 설명을 드리겠습니다.
자바스크립트의 Promise는 비동기 작업을 처리하기 위해 사용되며, then
과 catch
메서드를 통해 작업의 결과에 따라 후속 작업을 정의할 수 있습니다. 여기서 핵심은 then
메서드가 또 다른 Promise
를 반환함으로써 체이닝이 가능하다는 점입니다.
Promise 체이닝: then
메서드는 항상 Promise
객체를 반환합니다. 만약 then
메서드의 콜백 함수 내에서 새로운 Promise
객체를 반환하게 되면, 그 다음에 체이닝된 then
메서드는 새로운 Promise
가 처리될 때까지 기다리게 됩니다. 즉, then
에서 반환된 Promise의 이행(resolve) 또는 거부(reject)를 기다렸다가 그 다음 콜백을 실행하게 됩니다.
비동기 실행: then
메서드에 전달된 콜백 함수는 현재 콜스택이 비어질 때 이벤트 루프에 의해 실행됩니다. 이는 마이크로태스크 큐에 등록되어 기다리게 되고, 자바스크립트의 비동기 처리 모델에 따라 콜스택의 모든 실행이 마무리된 후에 실행됩니다.
코드 흐름 설명:
then
에서 1
이라는 값이 콘솔에 출력됩니다. 이곳에서 반환된 새로운 Promise
가 2
초 후에 이행될 때까지 기다립니다.then
에서 2
라는 값이 콘솔에 출력되지만, 이 Promise
는 1초 후 강제로 거부(reject)됩니다.catch
블록은 거부된 Promise
의 오류를 처리하게 되어 콘솔에 에러 메시지가 출력됩니다.관련 내용을 더 알아보고 싶으시다면, 자바스크립트의 프로미스 체이닝과 이벤트 루프에 대해 참고할 수 있는 강의를 추천드립니다:
- ProJavaScript 시리즈 - 프로미스 체이닝과 마이크로태스크 큐 이해하기
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.