6. 이러한 코드가 있다고 하자.
User.findOne({email: email}, function(err, user) {
if (err) {
console.error(err);
} else {
Group.find({owner: user.id}, function(err, groups) {
if (err) {
console.error(err);
} else {
try {
// do something with groups
console.log('success');
} catch (err) {
console.error(err);
}
}
});
});
7. 문제점 1. 중첩된 콜백과 if절 등으로 가독성이 떨어진다.
User.findOne({email: email}, function(err, user) {
if (err) {
console.error(err);
} else {
Group.find({owner: user.id}, function(err, groups) {
if (err) {
console.error(err);
} else {
try {
// do something with groups
console.log('success');
} catch (err) {
console.error(err);
}
}
});
});
8. 문제점 2. 동일한 에러 처리 로직이 중복되어 사용되었다.
User.findOne({email: email}, function(err, user) {
if (err) {
console.error(err);
} else {
Group.find({owner: user.id}, function(err, groups) {
if (err) {
console.error(err);
} else {
try {
// do something with groups
console.log('success');
} catch (err) {
console.error(err);
}
}
});
});
9. Promise를 이용해 바꾸어 보자. 더 길어지긴 했지만,
new Promise(function(resolve, reject) {
User.findOne({email: email}, function(err, user) {
if (err) reject(err);
else resolve(user);
});
})
.then(function(user) {
return new Promise(function(resolve, reject) {
Group.find({owner: user.id}, function(err, groups) {
if (err) reject(err);
else resolve(groups);
});
});
})
.then(function(groups) {
// do something with groups
})
.then(function() {
console.log('success');
})
.catch(function(err) {
console.error(err);
});
10. 개선점 1. 작업의 단계가 명확히 구분된다.
new Promise(function(resolve, reject) {
User.findOne({email: email}, function(err, user) {
if (err) reject(err);
else resolve(user);
});
})
.then(function(user) {
return new Promise(function(resolve, reject) {
Group.find({owner: user.id}, function(err, groups) {
if (err) reject(err);
else resolve(groups);
});
});
})
.then(function(groups) {
// do something with groups
})
.then(function() {
console.log('success');
})
.catch(function(err) {
console.error(err);
});
11. 개선점 2. 에러도 한군데에서 다 처리한다.
new Promise(function(resolve, reject) {
User.findOne({email: email}, function(err, user) {
if (err) reject(err);
else resolve(user);
});
})
.then(function(user) {
return new Promise(function(resolve, reject) {
Group.find({owner: user.id}, function(err, groups) {
if (err) reject(err);
else resolve(groups);
});
});
})
.then(function(groups) {
// do something with groups
})
.then(function() {
console.log('success');
})
.catch(function(err) {
console.error(err);
});
12. 더 개선해 보자. 사용자를 찾는 부분을..
new Promise(function(resolve, reject) {
User.findOne({email: email}, function(err, user) {
if (err) reject(err);
else resolve(user);
});
});
13. 다음과 같이 Wrapper 형태로 뽑아냈다고 가정해보자.
var UserWrapper = {
findOne: function(query) {
return new Promise(function(resolve, reject) {
User.findOne(query, function(err, user) {
if (err) reject(err);
else resolve(user);
});
});
}
};
14. 그룹들을 찾는 부분도 마찬가지로..
var GroupWrapper = {
find: function(query) {
return new Promise(function(resolve, reject) {
Group.find(query, function(err, groups) {
if (err) reject(err);
else resolve(groups);
});
});
}
};
15. 그러면 이런 식으로 바꿀 수 있다. 가독성이 훨씬 좋아졌다.
UserWrapper.findOne({ email: email })
.then(function(user) {
return GroupWrapper.find({ owner: user.id });
})
.then(function(groups) {
// do something with groups
})
.then(function() {
console.log('success');
})
.catch(function(err) {
console.error(err);
});
16. 참고로 이러한 변환 과정을 Promisification이라고 하며,
Bluebird에서 제공하는 Promise.promisify() 함수를 이용하면
더 쉽게 할 수 있다.
17. 극단적으로는 다음과도 같이 할 수 있다.
findUserByEmail(email)
.then(findOwningGroups)
.then(doSomethingWithGroups)
.then(whenSuccess)
.catch(whenFail);
주석이 필요없지 않은가? ㅋㅋ
19. 시작: Promise Chain을 시작하는 방법
가장 쉬운 방법 중 하나는 Promise.defer() 함수를 이용하는 방
법이지만 deprecated되었다.
function dont_do_like_this() {
var deferred = Promise.defer();
doSomethingAsync(function(err, result) {
if (err) return deferred.reject(err);
deferred.resolve(result);
});
return deferred.promise;
}
20. 그러므로 다음과 같이 Constructor를 이용하는 것이 더 바람직하
다.
function ok() {
return new Promise(function (resolve, reject) {
doSomethingAsync(function(err, result) {
if (err) return reject(err);
resolve(result);
});
});
}
21. Callback이 없다면 Promise.try() 함수를 이용할 수도 있다.
function checkSomething(something) {
return Promise.try(function () {
if (is_not_valid(something)) {
throw new Error('Something is not valid.');
}
return something;
});
}
22. 로직 없이 주어진 값이나 오브젝트로 바로 Promise를 생성할 수도
있다.
function startWithValue(value) {
return Promise.resolve(value);
}
심지어 에러로 Promise를 생성할 수도 있다. 쓸 일이 있을까?
function startWithError(error) {
return Promise.reject(error);
}
23. 중간: Promise Chain을 연결시키는 방법
Promise는 다음과 같이 .then() 함수와 .catch() 함수로 이어
나갈 수 있다.
ajaxGetAsync(url)
.then(function(result) {
return parseValueAsync(result);
})
.catch(function(error) {
console.error('error:', error);
});
24. 직전 Promise의 결과 값과 상관 없이 특정 값으로 Promise
Chain을 이어가려면..
somethingAsync()
.return(value);
위는 아래와 같다.
somethingAsync()
.then(function() {
return value;
});
25. 직전 Promise의 결과 값과 상관 없이 특정 에러로 Promise
Chain을 이어가려면..
somethingAsync()
.throw(reason);
위는 아래와 같다.
somethingAsync()
.then(function() {
throw reason;
});
31. 이렇게 하지 않도록 주의하라. (X)
somethingAsync()
.then(function(result) {
return anotherAsync(result);
}, function(err) {
handleError(err);
});
32. 이렇게 해야 한다. (O)
somethingAsync()
.then(function(result) {
return anotherAsync(result);
})
.catch(function(err) {
handleError(err);
});
33. 이렇게 하지 않도록 주의하라. (X)
var promise = somethingAsync();
if (is_another_necessary) {
promise.then(function(result) {
return anotherAsync(result);
});
}
promise.then(function(result) {
doSomethingWithResult(result);
});
34. 이렇게 해야 한다. (O)
var promise = somethingAsync();
if (is_another_necessary) {
promise = promise.then(function(result) {
return anotherAsync(result);
});
}
promise.then(function(result) {
doSomethingWithResult(result);
});
43. .map() 혹은 .filter()에서의 concurrency 제한에 대하여
Group.listAsync({user: user.id})
.map(function(group) {
return group.removeAsync(user.id);
}, {concurrency: 1});
44. 참고로 하나씩 처리하려면 이렇게 할 수도 있다.
Group.listAsync({user: user.id})
.reduce(function(promise, group) {
return promise.then(function(groups) {
return group.removeAsync(user.id)
.then(function(group) {
groups.push(group);
})
.return(groups);
});
}, []);
(확인은 안 해봤지만 아마도 틀리지 않을 듯;;)
50. 이런 식으로 앞의 결과를 나중에 써야할 경우엔 콜백에서처럼 가독
성이 떨어지는데..
somethingAsync()
.spread(function (a, b) {
return anotherAsync(a, b)
.then(function (c) {
return a + b + c;
});
});
51. 이런 식으로도 해결할 수 있지만..
var scope = {};
somethingAsync()
.spread(function (a, b) {
scope.a = a;
scope.b = b;
return anotherAsync(a, b);
})
.then(function (c) {
return scope.a + scope.b + c;
});