61
Promise and Bluebird Daniel Ku ( http://danielku.com/)

Promise and Bluebird

Embed Size (px)

Citation preview

Page 1: Promise and Bluebird

Promise and BluebirdDaniel Ku (http://danielku.com/)

Page 2: Promise and Bluebird

PromiseJavascript 코드 가독성을 높이기 위한 위한 베스트 프랙티스

Promise 스펙: https://promisesaplus.com/

Page 3: Promise and Bluebird

Bluebird가장 유명한 Promise 구현체 중 하나

https://github.com/petkaantonov/bluebird

Page 4: Promise and Bluebird

Node.js 사용 시

이렇게 선언하게 한다.

var Promise = require('bluebird');

나는 보통 다음과 같이 선언한다.

var Q = require('bluebird');

이유: 짧으니까

Page 5: Promise and Bluebird

맛보기

Page 6: Promise and Bluebird

이러한 코드가 있다고 하자.

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); } } });});

Page 7: Promise and Bluebird

문제점 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); } } });});

Page 8: Promise and Bluebird

문제점 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); } } });});

Page 9: Promise and Bluebird

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);});

Page 10: Promise and Bluebird

개선점 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);});

Page 11: Promise and Bluebird

개선점 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);});

Page 12: Promise and Bluebird

더 개선해 보자. 사용자를 찾는 부분을..

new Promise(function(resolve, reject) { User.findOne({email: email}, function(err, user) { if (err) reject(err); else resolve(user); });});

Page 13: Promise and Bluebird

다음과 같이 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); }); }); }};

Page 14: Promise and Bluebird

그룹들을 찾는 부분도 마찬가지로..

var GroupWrapper = { find: function(query) { return new Promise(function(resolve, reject) { Group.find(query, function(err, groups) { if (err) reject(err); else resolve(groups); }); }); }};

Page 15: Promise and Bluebird

그러면 이런 식으로 바꿀 수 있다. 가독성이 훨씬 좋아졌다.

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); });

Page 16: Promise and Bluebird

참고로 이러한 변환 과정을 Promisification이라고 하며, Bluebird에서 제공하는 Promise.promisify() 함수를 이용하면 더 쉽게 할 수 있다.

Page 17: Promise and Bluebird

극단적으로는 다음과도 같이 할 수 있다.

findUserByEmail(email) .then(findOwningGroups) .then(doSomethingWithGroups) .then(whenSuccess) .catch(whenFail);

주석이 필요없지 않은가? ㅋㅋ

Page 18: Promise and Bluebird

시작, 중간 그리고 끝

Page 19: Promise and Bluebird

시작: 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;}

Page 20: Promise and Bluebird

그러므로 다음과 같이 Constructor를 이용하는 것이 더 바람직하다.

function ok() { return new Promise(function (resolve, reject) { doSomethingAsync(function(err, result) { if (err) return reject(err); resolve(result); }); });}

Page 21: Promise and Bluebird

Callback이 없다면 Promise.try() 함수를 이용할 수도 있다.

function checkSomething(something) { return Promise.try(function () { if (is_not_valid(something)) { throw new Error('Something is not valid.'); } return something; });}

Page 22: Promise and Bluebird

로직 없이 주어진 값이나 오브젝트로 바로 Promise를 생성할 수도 있다.

function startWithValue(value) { return Promise.resolve(value);}

심지어 에러로 Promise를 생성할 수도 있다. 쓸 일이 있을까?

function startWithError(error) { return Promise.reject(error);}

Page 23: Promise and Bluebird

중간: Promise Chain을 연결시키는 방법

Promise는 다음과 같이 .then() 함수와 .catch() 함수로 이어나갈 수 있다.

ajaxGetAsync(url) .then(function(result) { return parseValueAsync(result); }) .catch(function(error) { console.error('error:', error); });

Page 24: Promise and Bluebird

직전 Promise의 결과 값과 상관 없이 특정 값으로 Promise Chain을 이어가려면..

somethingAsync() .return(value);

위는 아래와 같다.

somethingAsync() .then(function() { return value; });

Page 25: Promise and Bluebird

직전 Promise의 결과 값과 상관 없이 특정 에러로 Promise Chain을 이어가려면..

somethingAsync() .throw(reason);

위는 아래와 같다.

somethingAsync() .then(function() { throw reason; });

Page 26: Promise and Bluebird

.finally() 함수도 있다.

Promise.resolve('test') .catch(console.error.bind(console)) .finally(function() { return 'finally'; }) .then(function(result) { console.log(result); // result is 'test' });

Page 27: Promise and Bluebird

끝: Promise Chain을 끝내는 방법

가장 간단한 방법은 더 이상 Promise Chain을 연결하지 않는 것이다.

Page 28: Promise and Bluebird

하지만 명시적으로 .done() 함수를 쓸 수 있다.

somethingAsync() .done();

Page 29: Promise and Bluebird

Alias

Promise.try() === Promise.attemp()

.catch() === .caught()

.return() === .thenReturn()

.throw() === .thenThrow()

.finally() === .lastly()

Page 30: Promise and Bluebird

몇가지 안티 패턴

Page 31: Promise and Bluebird

이렇게 하지 않도록 주의하라. (X)

somethingAsync() .then(function(result) { return anotherAsync(result); }, function(err) { handleError(err); });

Page 32: Promise and Bluebird

이렇게 해야 한다. (O)

somethingAsync() .then(function(result) { return anotherAsync(result); }) .catch(function(err) { handleError(err); });

Page 33: Promise and Bluebird

이렇게 하지 않도록 주의하라. (X)

var promise = somethingAsync();if (is_another_necessary) { promise.then(function(result) { return anotherAsync(result); });}promise.then(function(result) { doSomethingWithResult(result);});

Page 34: Promise and Bluebird

이렇게 해야 한다. (O)

var promise = somethingAsync();if (is_another_necessary) { promise = promise.then(function(result) { return anotherAsync(result); });}promise.then(function(result) { doSomethingWithResult(result);});

Page 35: Promise and Bluebird

편리한 유틸리티 함수들

Page 36: Promise and Bluebird

.all()

.any()

.some()

.spread()

Page 37: Promise and Bluebird

설명이 필요 없다.

Promise .all([ pingAsync("ns1.example.com"), pingAsync("ns2.example.com"), pingAsync("ns3.example.com"), pingAsync("ns4.example.com") ]).spread(function(first, second, third, fourth) { console.log(first, second, third, fourth); });

Page 38: Promise and Bluebird

설명이 필요 없다.

Promise .any([ pingAsync("ns1.example.com"), pingAsync("ns2.example.com"), pingAsync("ns3.example.com"), pingAsync("ns4.example.com") ]).spread(function(first) { console.log(first); });

Page 39: Promise and Bluebird

설명이 필요 없다.

Promise .some([ pingAsync("ns1.example.com"), pingAsync("ns2.example.com"), pingAsync("ns3.example.com"), pingAsync("ns4.example.com") ], 2).spread(function(first, second) { console.log(first, second); });

Page 40: Promise and Bluebird

.map()

.reduce()

.filter()

.each()

Page 41: Promise and Bluebird

설명이 필요 없다.

Group.listAsync({user: user.id}) .map(function(group) { return group.removeAsync(user.id); }) .then(function() { return user.removeAsync(); });

Page 42: Promise and Bluebird

설명이 필요 없다.

Group.listAsync({user: user.id}) .filter(function(group) { return group.owner_id === user.id; });

Page 43: Promise and Bluebird

.map() 혹은 .filter()에서의 concurrency 제한에 대하여

Group.listAsync({user: user.id}) .map(function(group) { return group.removeAsync(user.id); }, {concurrency: 1});

Page 44: Promise and Bluebird

참고로 하나씩 처리하려면 이렇게 할 수도 있다.

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); }); }, []);

(확인은 안 해봤지만 아마도 틀리지 않을 듯;;)

Page 45: Promise and Bluebird

.delay()

Page 46: Promise and Bluebird

설명이 필요 없다.

Promise .delay(500) .then(function() { return 'Hello world'; }) .delay(500) .then(function(result) { console.log(result); });

Page 47: Promise and Bluebird

.tap()

Page 48: Promise and Bluebird

Promise Chain을 연결하긴 하지만 결과를 넘겨주진 않음..

somethingAsync() .tap(function(result_of_somethingAsync) { return anotherAsync(result_of_somethingAsync); }) .then(function(result_of_somethingAsync) { console.log(result_of_somethingAsync); });

Page 49: Promise and Bluebird

.bind()

Page 50: Promise and Bluebird

이런 식으로 앞의 결과를 나중에 써야할 경우엔 콜백에서처럼 가독성이 떨어지는데..

somethingAsync() .spread(function (a, b) { return anotherAsync(a, b) .then(function (c) { return a + b + c; }); });

Page 51: Promise and Bluebird

이런 식으로도 해결할 수 있지만..

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; });

Page 52: Promise and Bluebird

이게 더 깔끔하다.

somethingAsync() .bind({}) .spread(function (a, b) { this.a = a; this.b = b; return anotherAsync(a, b); }) .then(function (c) { return this.a + this.b + c; });

Page 53: Promise and Bluebird

고급 예제

Page 54: Promise and Bluebird

10초 마다 어떤 작업을 반복하도록 하려면?

Page 55: Promise and Bluebird

10초 마다 어떤 작업을 반복하도록 하려면?

var loop = function() { Worker.executeAsync() .catch(function(err) { console.error('err:', err); }) .delay(10000) .then(loop);};

loop();

Page 56: Promise and Bluebird

매우 긴 작업 목록을 10개씩 끊어서 처리하려면?

Page 57: Promise and Bluebird

매우 긴 작업 목록을 10개씩 끊어서 처리하려면?

var traverseJobs = function(handleTenJobsAsync) { var limit = 10;

var loop = function(skip) { return listJobsAsync(limit, skip) .then(function(jobs) { if (jobs.length) { return handleTenJobsAsync(jobs) .then(function() { return loop(skip + limit); }); } }); };

return loop(0);};

Page 58: Promise and Bluebird

못다한 고급 주제

Page 59: Promise and Bluebird

— Resource Management

— Cancellation & .timeout()

— Built-in Errors

— Error Management configuration

— Generators

https://github.com/petkaantonov/bluebird/blob/master/API.md

Page 61: Promise and Bluebird

THE END