- Promise는 비동기 작업의 완료 결과를 알려주는 객체이다.
- new Promise에 전달되는 함수(실행자 함수, executor)는 자동으로, 동기적으로 실행되며
resolve, reject 라는 2개의 콜백함수를 받는다. - executor 함수의 작업이 끝나는 대로 결과에 따라서 resolve / reject 중 하나의 함수를 호출한다.
- 정확히는 작업이 성공했을 경우, 결과를 나타내는 value와 함께 resolve(value) 호출
- resolve는 Promise가 성공적으로 완료되었을 때 결과를 반환하기 위해 설계된 내부 콜백 함수
- resolve() 호출 = Promise 완료(성공)
- 에러 발생했을 경우 에러 객체를 나타내는 error와 함께 reject(error) 호출
- then은 비동기작업이 끝나고 실행될 함수이며, 실행결과에 따른 값을 받아서 실행된다.
- new Promise가 반환한 프로미스 객체는 state, result라는 내부 프로퍼티를 갖는다.
- state: pending -> fulfilled / reject
- result: undefined -> value / error
- 프로미스 작업은 MicroTask Queue에 들어가서 처리되기 때문에 프로미스 핸들링은 항상 비동기로 처리된다.
- cf. macrotask queue는 프로미스와 직접적 연관은 없지만,
자바스크립트 엔진은 매크로태스크 하나를 처리할 때마다 다른 매크로태스크나 렌더링을 작업하기 전에 마이크로태스크큐에 쌓인 태스크를 전부 처리한다.- Event Loop의 알고리즘:
- macrotask queue에서 가장 오래된 태스크를 꺼내 실행
- 모든 microtask를 실행(큐가 빌 때까지)
- 렌더링할 것이 있으면 처리(Animation Frames)
- macrotask queue가 비어있으면 새로운 macrotask가 나타날 때까지 기다림
- 이 순서를 반복
- 즉, Microtask Queue > Animation Frames > Macrotask Queue 의 우선순위로 콜스택에 이동된다.
- Promise -> Microtask Queue
- RequestAnimationFrame -> Animation Frame
- Timeout -> Task Queue
- Event Loop의 알고리즘:
const promise = new Promise((resolve, reject) => {
// executor ...
console.log('executor');
// setTimeout(() => resolve('결과'), 2000);
setTimeout(() => reject('결과'), 2000);
});
promise //
.then(
(result) => console.log('resolve', result),
(result) => console.log('reject', result),
);
executor
(2초 뒤)
reject 결과
위는 이해를 위한 간단한 예시이며 reject에서는 보통 에러를 다음과 같이 처리한다.
const promise = new Promise((resolve, reject) => {
// executor ...
console.log('executor');
// setTimeout(() => resolve('결과'), 2000);
setTimeout(() => reject(new Error('에러')), 2000);
});
promise //
.then(
(result) => console.log('resolve', result),
(result) => console.log('reject', result),
);
executor
(2초 뒤)
reject Error: 에러
비동기 작업 후처리 할 때
then에 첫 번째 인자는 성공했을 경우 처리할 함수, 두 번째 인자에는 실패했을 경우 처리할 함수를 처리할 수 있는데
이 방법보다는 catch를 사용하여 에러 처리를 하는 것이 더 직관적
const promise = new Promise((resolve, reject) => {
// executor ...
console.log('executor');
// setTimeout(() => resolve('결과'), 2000);
setTimeout(() => reject(new Error('에러')), 2000);
});
promise //
.then((result) => console.log('resolve', result))
.catch((error) => console.log('reject', error));
지금까지 위 예시에서는 resolve를 주석처리했는데, 이 부분을 해제한다면
const promise = new Promise((resolve, reject) => {
// executor ...
console.log('executor');
setTimeout(() => resolve('결과'), 2000);
setTimeout(() => reject(new Error('에러')), 2000);
});
promise //
.then((result) => console.log('resolve', result))
.catch((error) => console.log('reject', error));
executor
(2초 후)
resolve 결과
의 결과를 반환한다. 그 이유는 resolve 함수가 더 위에 있기 때문.
만약 reject가 먼저 실행되는 코드라면 에러를 반환한다.
아무튼 결론은 promise의 executor는 하나의 콜백함수만 호출한다는 것이다.
const promise = new Promise((resolve, reject) => {
// executor ...
console.log('executor');
resolve(123);
});
promise //
.then((result) => console.log('resolve', result))
.catch((error) => console.log('reject', error));
executor
resolve 123
추가로, 성공 여부에 상관없이 작업이 종료되면 실행될 finally
성공여부를 몰라도 되기 때문에 전달하는 인수가 없다.
const promise = new Promise((resolve, reject) => {
// executor ...
console.log('executor');
// setTimeout(() => resolve('결과'), 2000);
setTimeout(() => reject(new Error('에러')), 2000);
});
promise //
.finally(() => console.log('필수'))
.then((result) => console.log('resolve', result))
.catch((error) => console.log('reject', error));
executor
(2초 후)
필수
reject Error: 에러
프로미스 체이닝
다음 코드는 2초 뒤 1 2 4를 호출한다.
new Promise(function (resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
})
.then(function (result) {
console.log(result); // 1
return result * 2;
})
.then(function (result) {
console.log(result); // 2
return result * 2;
})
.then(function (result) {
console.log(result); // 4
return result * 2;
});
- result가 핸들러 체인을 통해 다음 .then 핸들러로 전달됨
- 프로미스 체이닝이 가능한 이유는 promise.then을 호출하면 프로미스가 반환되기 때문
- 핸들러가 값을 반환할 때 이 값이 프로미스의 result가 됨
- 핸들러가 프로미스를 생성하거나 반환할 수도 있다.
new Promise(function (resolve, reject) {
setTimeout(() => resolve(1), 2000);
})
.then(function (result) {
console.log(result); // 1
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 3000);
});
})
.then(function (result) {
console.log(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 4000);
});
})
.then(function (result) {
console.log(result); // 4
});
(2초 후) 1
(3초 후) 2
(4초 후) 4
async await
- async 키워드를 붙이면 해당 함수는 프로미스를 반환한다. 프로미스가 아닌 것은 프로미스로 감싸 반환
- await 키워드는 프로미스가 처리될 때까지 기다리고 결과를 반환한다.
- 이는 Promise보다 가독성 좋고 쓰기도 쉽다.
참조:
https://ko.javascript.info/promise-basics
프라미스
ko.javascript.info
https://joshua1988.github.io/web-development/javascript/promise-for-beginners/
자바스크립트 Promise 쉽게 이해하기
(중급) 자바스크립트 입문자를 위한 Promise 설명. 쉽게 알아보는 자바스크립트 Promise 개념, 사용법, 예제 코드. 예제로 알아보는 then(), catch() 활용법
joshua1988.github.io
+
모던 자바스크립트 Deep Dive를 보고 공부한 내용을 추가로 노션에 정리:
Promise, fetch, async/await 복습(w. axios, Modern JS Deep Dive) | Notion
프로미스의 기본
mnmhbbb.notion.site
'JavaScript > etc' 카테고리의 다른 글
| JavaScript Engine - 4. 블럭 스코프와 매개변수 (0) | 2023.07.01 |
|---|---|
| commonjs vs module (0) | 2022.06.13 |
| JavaScript Engine - 5. 비동기 시작(callback, promise) (0) | 2022.03.23 |
| JavaScript Engine - 3. this (0) | 2022.03.23 |
| JavaScript Engine - 2(호출 스택, 스코프, TDZ, 선언, 초기화...) (0) | 2022.03.22 |