우선 비동기 처리에 대해 짧게 이야기하고 넘어가겠습니다. 자세한 이야기는 이 포스트를 참조해주세요.
비동기 처리
는 데이터의 처리를 서버단에서 수행하고, 클라이언트측(브라우저)에서는 사용자와의 상호작용만을 하는 처리 방식입니다. 이 방식덕에 사용자는 요청 후 응답까지 기다리지 않아도 되고, 새로고침(리프레쉬)같은 동작이 없다는 것이 큰 장점입니다. 그래서 현대의 웹사이트에서는 비동기 처리
방식이 대세를 이루게 되었고 자바스크립트는 비동기 처리를 위한 다양한 객체들을 지원하고 있습니다. 그 중 가장 많이 사용되는 방식이 오늘 다루는 Promise 객체
입니다.
콜백 함수
가 사실은 여러번 등장했던 단어인데, 다시 한 번 더 짚고 넘어가겠습니다. 콜백 함수(Callback function)
는 특정 시점에서 호출되는 함수입니다. 명시적으로 호출되는 일반 함수와는 다르게 등록되어 있다가 특정 조건(시점)을 만족하면 호출이됩니다.
그동안 자바스크립트에서는 비동기 처리
를 위해서 콜백 함수
를 사용했었습니다. 하지만, 콜백 함수
는 비동기 처리가 중첩될 때마다 코드나 무척 복잡해지는 경향이 있었습니다.
1번 콜백 함수 {
2번 콜백 함수 {
3번 콜백 함수 {
4번 콜백 함수 {
5번 콜백 함수 {
}
}
}
}
}
위와 같은 현상을 콜백 지옥
이라고 부릅니다. 이 콜백 지옥을 해결하기 위해 등장한 대안이 바로 Promise 객체
입니다.
Promise 객체
는 비동기 처리를 하는 객체입니다. 기본적으로 다음과 같은 구성을 갖습니다.
const promise = new Promise((resolve, reject) => {
//처리 내용
});
프로미스 객체의 인수로는 함수를 받는데, 이 함수 내부에 비동기 처리 코드를 작성합니다. 그리고 이 함수의 인자인 resolve
와 reject
가 눈에 띄는데, resolve
는 비동기 처리의 성공을 알리는, reject
는 비동기 처리의 실패를 알리는 함수입니다. 결과에 따라 resolve
와 reject
를 Promise 객체가 알아서 전달해주기에, 크게 신경쓰지 않아도 됩니다.
비동기 처리의 성공 결과인 resolve
를 받는 것은 then 메소드
입니다. 실패 결과인 reject
는 catch 메소드
가 받게됩니다. 비동기 콜백 작업 후에 실행하게 해주는 finally
도 존재합니다.
아래 코드가 Promise 객체
를 만들고 사용하는 코드입니다. Promise 객체
에 비동기 처리 내용을 기술하고, 해당 결과에 따라서 then, catch 메소드를 실행합니다.
const promise = new Promise((resolve, reject)=>{
//처리 내용
});
promise.then(
//resolve가 호출되면 then이 실행
)
.catch(
//reject가 호출되면 catch가 실행
)
.finally(
//콜백 작업을 마치고 무조건 실행되는 finally (생략 가능)
)
한 번 구체적인 예제 코드로 Promise 객체
를 이해해보겠습니다.
const flag = true;
const promise = new Promise(((resolve, reject) => {
if (flag) {
resolve('resolve가 되었음');
}
else {
reject('reject가 되었음');
}
}));
promise.then((resolveMessage) => {
console.log(resolveMessage);
})
.catch((errorMessage) => {
console.log(errorMessage);
});
논리형 변수 flag가 true면 resolve를 호출하고, false면 reject를 호출하도록 비동기 처리를 했는데, flag가 true이므로 then 메소드의 코드가 실행되었음을 볼 수 있습니다.
then, catch 메소드에 또 다른 then, catch 메소드를 이어붙임으로써 여러 비동기 처리를 연결할 수 있습니다.
방금 전의 예제 코드를 변형시켜, 두 개의 비동기 처리를 서로 연결시켜보겠습니다.
const flag = true;
const promise = new Promise(((resolve, reject) => {
if (flag) {
resolve('resolve가 되었음');
}
else {
reject('reject가 되었음');
}
}));
promise.then((resolveMessage) => {
console.log(resolveMessage);
return new Promise(((resolve, reject) => {
if (flag) {
resolve('resolve가 되었음2');
}
else {
reject('reject가 되었음2');
}
}));
})
.then((resolveMessage2)=> {
console.log(resolveMessage2);
})
.catch((errorMessage) => {
console.log(errorMessage);
});
처음으로 then 메소드를 실행하는 구문에서 return 명령으로 또 다른 Promise 객체를 반환합니다. 그 다음 뒤에 then 메소드를 더 이어서 반환된 Promise 객체에 대한 비동기 처리 결과를 실행합니다.
promise.then((resolveMessage) => {
console.log(resolveMessage);
return new Promise(((resolve, reject) => {
if (flag) {
resolve('resolve가 되었음2');
}
else {
reject('reject가 되었음2');
}
}));
})
.then((resolveMessage2)=> {
console.log(resolveMessage2);
})
방금 전에 본 방식은 스크립트가 위에서 아래로 실행되며 해석되기에 순차적으로 비동기 처리를 했습니다. all
메소드를 사용하면, 여러 비동기 처리를 병행적으로 수행할 수 있습니다.
Promise.all(
//병행 처리할 Promise 객체들
)
메소드의 인수로는 대괄호[]
를 사용해서 프로미스 객체들을 전달합니다. all
메소드를 이용한 여러 개의 Promise 객체들을 병렬 처리하는 것을 예제로 보겠습니다.
const promise1 = new Promise((resolve, reject) => {
resolve('resolve 되었음1');
});
const promise2 = new Promise((resolve, reject) => {
resolve('resolve 되었음2');
});
const promise3 = new Promise((resolve, reject) => {
resolve('resolve 되었음3');
});
Promise.all([
promise1,
promise2,
promise3,
])
.then((message) => {
console.log(message);
})
.catch((errorMessage) => {
console.log(errorMessage);
});
지금은 모두 resolve를 반환하기에 then 메소드를 실행했습니다.
만약 all
메소드로 전달된 Promise 객체들 중 하나라도 reject를 호출한다면, catch 메소드가 실행됩니다.
Promise.race {
//프로미스 객체들
}
race
메소드는 all 메소드처럼 여러 프로미스들을 전달하고, 그 중에서 하나가 먼저 완료됐을 경우 then 메소드를 호출합니다.
이전의 all 메소드를 race 메소드로 교체했을 때 무슨일이 일어나는지 보여드리겠습니다.
const promise1 = new Promise((resolve, reject) => {
resolve('resolve 되었음1');
});
const promise2 = new Promise((resolve, reject) => {
resolve('resolve 되었음2');
});
const promise3 = new Promise((resolve, reject) => {
resolve('resolve 되었음3');
});
Promise.race([
promise1,
promise2,
promise3,
])
.then((message) => {
console.log(message);
})
.catch((errorMessage) => {
console.log(errorMessage);
});
코드 순서상 먼저온 promise1이 실행되자, then을 실행한 모습입니다. 나머지 프로미스들은 성공했다고 하더라도 나타나지는 않습니다.
Promise.resolve(value);
Promise.reject(reason);
Promise.resolve
와 Promise.reject
는 각각 resolve와 reject를 즉시 호출하는 메소드입니다. resolve
는 then을 실행하며, 인수로 전달된 값을 함께 넘겨주고, reject
는 인수로 주는 이유를 catch에 보냅니다.. 코드의 결과에 상관없이 이 두 메소드를 이용하면, 원하는 프로미스 결과를 얻을 수 있습니다.
const promise1 = Promise.resolve('resolve가 되었음');
const promise2 = Promise.reject('reject가 되었음');
promise1.then((resolveMessage)=>{
console.log('resolve: ' + resolveMessage);
})
.catch((rejectMessage)=>{
console.log('reject: ' + rejectMessage);
});
promise2.then((resolveMessage)=>{
console.log('resolve: ' + resolveMessage);
})
.catch((rejectMessage)=>{
console.log('reject: ' + rejectMessage);
});