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 순서로 출력된다.