2.1.6 프로미스
자바스크립트와 노드에서는 주로 비동기 프로그래밍을 합니다.
특히 이벤트 주도 방식 때문에 콜백 함수를 자주 사용합니다.
ES2015 부터는 자바스크립트와 노드의 API들이 콜백 대신 프로미스(promise) 기반으로 재구성됩니다.
그래서 악명 높은 콜백 헬(callback hell)을 극복했다는 평가를 받고 있습니다.
프로미스는 다음과 같은 규칙이 있습니다.
먼저 프로미스 객체를 생성해야 합니다.
const condition = true; // true면 resolve, false면 reject
const promise = new Promise((resolve, reject) => {
if (condition) {
resolve('성공');
} else {
reject('실패');
}
});
promise
.then((message) => {
console.log(message); // 성공(resolve)한 경우 실행
})
.catch((error) => {
console.error(error); // 실패(reject)한 경우 실행
})
new Promise
로 프로미스를 생성할 수 있으며, 안에 resolve
와 reject
를 매개변수로 갖는 콜백 함수를 넣어줍니다.
이렇게 만든 promise
변수에 then
과 catch
메서드를 붙일 수 있습니다.
프로미스 내부에서 resolve
가 호출되면 then
이 실행되고, reject
가 호출되면 catch
가 실행됩니다.
resolve
와 reject
에 넣어준 인자는 각각 then
과 catch
의 매개변수에서 받을 수 있습니다.
즉, resolve('성공')
가 호출되면 then
의 message
가 ‘성공’이 됩니다.
만약 reject('실패')
가 호출되면 catch
의 error
가 ‘실패’가 되는 것입니다.
condition
변수를 false
로 바꿔보면 catch
에서 에러가 로깅됩니다.
then
이나 catch
에서 다시 다른 then
이나 catch
를 붙일 수 있습니다.
이전 then
의 return
값을 다음 then
의 매개변수로 넘깁니다.
프로미스를 return
한 경우 프로미스가 수행된 후 다음 then
이나 catch
가 호출됩니다.
const condition = true; // true면 resolve, false면 reject
const promise = new Promise((resolve, reject) => {
if (condition) {
resolve('성공');
} else {
reject('실패');
}
});
promise
.then((message) => {
return new Promise((resolve, reject) => {
resolve(message);
});
})
.then((message2) => {
console.log(message2);
return new Promise((resolve, reject) => {
resolve(message2);
})
})
.then((message3) => {
console.log(message3);
})
.catch((error) => {
console.error(error); // 실패(reject)한 경우 실행
})
처음 then에서 message를 resolve하면 다음 then에서 받을 수 있습니다.
여기서 다시 message2를 resolve했으므로 다음 then에서 message3를 받았습니다.
이것을 활용해서 콜백을 프로미스로 바꿀 수 있습니다.
다음은 콜백을 쓰는 패턴중 하나입니다.
이를 프로미스로 바꿔보겠습니다.
function findAndSaveUser (Users) {
Users.findOne({}, (err, user) => { // 첫 번째 콜백
if (err) {
return console.error(err);
}
user.name = 'zero';
user.save((err) => { // 두 번째 콜백
if (err) {
return console.error(err);
}
Users.findOne({ gender: 'm' }, (err, user) => { // 세 번째 콜백
// 생략
})
})
})
}
콜백 함수가 세 번 중첩되어 있습니다.
콜백 함수가 나올 때마다 코드의 깊이가 깊어집니다.
각 콜백 함수마다 에러도 따로 처리해줘야 합니다.
이 코드를 다음과 같이 바꿀 수 있습니다.
이 코드를 다음과 같이 바꿀 수 있습니다.
function findAndSaveUser(Users) {
Users.findOne({})
.then((user) => {
user.name = 'zero';
return user.save();
})
.then((user) => {
return Users.findOne({ gender: 'm' });
})
.then((user) => {
// 생략
})
.catch(err => {
console.error(err);
})
}
코드의 깊이가 더 이상 깊어지지 않습니다.
then 메서드들은 순차적으로 실행됩니다.
콜백에서 매번 따로 처리해야 했던 에러도 마지막 catch에서 한번에 처리할 수 있습니다.
하지만 모든 콜백 함수를 위와 같이 바꿀 수 있는 것은 아닙니다.
메서드가 프로미스 방식을 지원해야 합니다.
예제의 코드는 findOne
과 save
메서드가 내부적으로 프로미스 객체를 가지고 있어서 가능한 것입니다.
지원하지 않는 경우 프로미스로 바꿀 수 있는 방법은 3.5.6절에 나와 있습니다.
마지막으로 프로미스 여러 개를 한번에 실행할 수 있는 방법이 있습니다.
기존의 콜백 패턴이었다면 콜백을 여러 번 중첩해서 사용해야 했을 것입니다.
하지만 Promise.all
을 활용하면 간단히 할 수 있습니다.
const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
Promise.all([promise1, promise2])
.then((result) => {
console.log(result); // ['성공1', '성공2']
})
.catch((error) => {
console.error(error);
});
Promise.resolve
는 즉시 resolve
하는 프로미스를 만드는 방법입니다.
비슷한 것으로 즉시 reject
하는 Promise.reject
도 있습니다.
프로미스가 여러 개 있을 때 Promise.all
에 넣으면 모두 resolve
될 때까지 기다렸다가 then
으로 넘어갑니다.
result
매개변수에 각각의 프로미스 결과값이 배열로 들어 있습니다.
Promise
중 하나라도 reject
가 되면 catch
로 넘어갑니다.