JavaScript의 콜백 함수는 비동기 처리를 위한 하나의 패턴으로, 아래와 같은 단점이 존재한다.
Promise
는 이를 보완하기 위해 ES6에서 도입된 또 다른 비동기 처리 패턴이다. Promise
의 장점은 다음과 같다.
Promise
생성자 함수를 new
연산자화 함께 호출하면 Promise 객체가 생성된다.
resolve
, reject
함수를 인수로 전달할 수 있다.
resolve
함수에 인수로 전달해 호출reject
함수에 인수로 전달해 호출Promise
는 비동기 처리 상태 정보를 가진다.
pending
- 비동기 처리가 수행되지 않은 상태fulfilled
- 성공rejected
- 실패const promise = new Promise((resolve, reject) => {
if (/* success */) {
resolve('result');
} else { /* fail */
reject('reason for failure');
}
});
프로미스의 비동기 처리 상태가 변화하면 후속 처리 메서드에 인수로 전달한 콜백 함수가 선택적으로 호출된다.
Promise.prototype.then
두 개의 콜백 함수를 인수로 전달받는다.
Promise
를 반환한다.
※ 에러를 then
의 두 번째 인수를 사용해 처리하면 가독성이 좋지 않을 뿐더러 then
의 첫 번째 인수인 콜백 함수에서 발생한 에러는 처리할 수 없다. 따라서 에러 처리는 아래 catch
를 사용해서 하는 것을 권장한다.
Promise.prototype.catch
한 개의 콜백 함수를 인수로 전달받는다.
rejected
상태인 경우만 호출된다.
Promise
를 반환한다.
Promise.prototype.finally
한 개의 콜백 함수를 인수로 전달받는다.
성공, 실패 여부 관계 없이 무조건 한 번 호출된다.
Promise
를 반환한다.
then
, catch
, finally
는 언제나 Promise
를 반환하기 때문에 연속적으로 호출이 가능하고,
이를 프로미스 체이닝
이라고 한다.
promise()
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('finished'));
Promise
는 5가지 정적 메서드를 제공한다.
Promise.resolve
/ Promise.reject
이미 존재하는 값으로 Promise
를 생성하기 위해 사용된다.
const resolvedPromise = Promise.resolve([1, 2, 3]);
// const resolvedPromise = new Promise(resolve => resolve([1, 2, 3])); // same as the above
resolvedPromise.then(console.log)); // [1, 2, 3]
Promise.all
여러 개의 비동기 코드를 병렬적으로 처리할 때 사용한다.
const reqData1 = () => new Promise(res => setTimeout(() => res(1), 1000));
const reqData2 = () => new Promise(res => setTimeout(() => res(2), 1000));
const reqData3 = () => new Promise(res => setTimeout(() => res(3), 1000));
Promise.all([reqData1(), reqData2(), reqData3()]
.then(console.log) // [1, 2, 3] => 약 1초 소요
모든 Promise
가 fulfilled
가 되면 종료된다.
처리 순서를 보장한다.
하나라도 rejected
되면 나머지를 기다리지 않고 즉시 종료된다.
전달받은 이터러블의 요소가 Promise
가 아닌 경우 Promise.resolve
를 통해 래핑된다.
Promise.race
Promise.all
과 동일하게 Promise
를 요소로 갖는 이터러블을 인수로 전달받는다.
Promise.all
과 다른 점은 모든 Promise
를 기다리는 대신 가장 먼저 fulfilled
된 프로미스 처리 결과를 resolve
하는 새로운 Promise
를 반환한다.
하나라도 rejected
되면 에러를 reject
하는 새로운 Promise
를 즉시 반환한다.
const reqData1 = () => new Promise(res => setTimeout(() => res(1), 3000));
const reqData2 = () => new Promise(res => setTimeout(() => res(2), 2000));
const reqData3 = () => new Promise(res => setTimeout(() => res(3), 1000));
Promise.race([reqData1(), reqData2(), reqData3()]
.then(console.log) // 3
Promise.allSettled
Promise
를 요소로 갖는 이터러블을 인수로 전달받는다.
모두 settled
상태 (fulfilled
/rejected
)가 되면 결과를 배열로 반환한다.
const reqData1 = () => new Promise(res => setTimeout(() => res(1), 2000));
const reqData2 = () => new Promise((_, reject) => setTimeout(() => reject(new Error('error')), 1000));
Promise.allSettled([reqData1(), reqData2()]
.then(console.log)
/*
{
{status: "fulfilled", value: 1},
{status: "rejected", reason: Error: error at ...}
}
*/
마이크로태스크 큐
에는 Promise
의 후속 처리 메서드의 콜백 함수가 일시 저장된다.
태스크 큐
에는 그 외의 비동기 함수의 콜백 함수나 이벤트 핸들러가 일시 저장된다.
마이크로태스크 큐
는 태스크 큐
보다 우선순위가 높다.
이벤트 루프는 콜 스택이 비면 먼저 마이크로태스크 큐
에서 대기하고 있는 함수를 가져와 실행하고, 이후 태스크 큐
에서 대기하고 있는 함수를 실행한다.
setTimeout(() => console.log(1), 0);
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
따라서 위의 코드는 2 → 3 → 1 순서로 출력된다.