Sweet Patterns for Promises
Promises/A+
ok?
One Choice
Bluebird
/petkaantonov/bluebird
It's the FASTEST
and robust
Look it up!
What are Promises?
Manage Asynchronicity
- Single callback guarantee
- Robust error handling
- Composability
Single Callback Guarantee
entity.create()
.then(function(udo) {
// once for either outcome
})
.catch(function(error) {
// once for either outcome
});
Robust Error Handling
entity.create()
.then(function(udo) {
// any error that happens in here
// will get caught in ".catch()"
throw new Error('ops');
})
.catch(function(error) {});
Composability
entity.create()
.then(entity.read)
.then(entity.parseForApi)
.then(entity.process)
.catch(entity.onError);
Creating Promises
2 ways
Creating a Novel Promise
Typically interfacing with vanilla callbacks
var Promise = require('bluebird');
/* ... */
ClipEntity.prototype.fetchVideos = function() {
return new Promise(function(resolve, reject) {
fs.read(file, function(err, res) {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
};
Interfacing with Promises
When your interfaces are all returning promises
var Promise = require('bluebird');
/* ... */
ClipEntity.prototype.showVideos = Promise.method(function() {
return this.fetchVideos();
});
Error Handling with new
Always use reject()
return new Promise(function(resolve, reject) {
if (someCondition) {
var err = new Error('Not good');
reject(err);
}
});
Error Handling with method()
Throw away
ClipEntity.prototype.showVideos = Promise.method(function() {
if(someCondition) {
throw new Error('ops');
}
});
REMEMBER
returning a Promise is CRITICAL!
- But you may return any value as well
Promises will only
return one argument
Using method()
Anything you return will be the resolving value
Foo.prototype.one = Promise.method(function() {
return this.two();
});
Foo.prototype.two = Promise.method(function() {
return 5;
});
/* ... */
foo.one()
.then(function(res) {
console.log(res); // 5
});
Working with Promises
Consuming a Promise
entity.create({name: 'thanasis'})
.then(function(udo) {
udo.name === 'thanasis'; // true
})
.catch(function(error) {
// deal with error.
});
Composing
ClipEntity.prototype._process = function(data) {
var clipProcessEnt = new ClipProcessEnt(data);
return clipProcessEnt.parseFiles()
.bind(clipProcessEnt)
.then(clipProcessEnt.isFileVideo)
.then(clipProcessEnt.storeClip)
.then(clipProcessEnt.storeThumb)
.then(clipProcessEnt.updateDatabase)
.then(clipProcessEnt.removeSourceFiles)
.catch(clipProcessEnt.rollback);
};
Create master methods
<--- Notice the .bind() !!
Error Chaining
Process.prototype.rollback = Promise.method(function(err) {
log.finer('rollback() :: Removing Clip from DB, clipId:', this.clipId);
return this.clipEnt.delete(this.clipId)
.bind(this)
.catch(function (error) {
log.warn('rollback() :: NOT GOOD ERROR. File:', this.sourceFilePath,
' Failed to remove record for clip with ID:', this.clipId, 'Error:', error);
throw error;
})
.then(function() {
throw err;
});
});
The Rollback
<--- From clipEnt.delete()
<--- From Parent
Returning Custom Value
ClipEntity.prototype._process = function(data) {
var clipProcessEnt = new ClipProcessEnt(data);
return clipProcessEnt.parseFiles()
.bind(clipProcessEnt)
.then(clipProcessEnt.isFileVideo)
.then(clipProcessEnt.storeClip)
.then(clipProcessEnt.storeThumb)
.then(clipProcessEnt.updateDatabase)
.then(clipProcessEnt.removeSourceFiles)
.return(data)
.catch(clipProcessEnt.rollback);
};
<-- Here it is!
Running in Parallel
ClipEntity.prototype._process = function(data) {
var clipProcessEnt = new ClipProcessEnt(data);
var promises = [];
promises.push(clipProcessEnt.isFileVideo());
promises.push(clipProcessEnt.storeClip());
promises.push(clipProcessEnt.storeThumb());
promises.push(clipProcessEnt.updateDatabase());
return Promise.all(promises)
.catch(clipProcessEnt.rollback);
};
Running in Parallel
ClipEntity.prototype._process = function(data) {
var clipProcessEnt = new ClipProcessEnt(data);
var promises = [];
promises.push(clipProcessEnt.isFileVideo());
promises.push(clipProcessEnt.storeClip());
promises.push(clipProcessEnt.storeThumb());
promises.push(clipProcessEnt.updateDatabase());
return Promise.all(promises, {concurrency: 10})
.catch(clipProcessEnt.rollback);
};
... with a throttle
Applying an Array to a Promise
// Compact
ClipExport.prototype._copyClips = Promise.method(function (filenames) {
return Promise.map(filenames, this._copyClip.bind(this));
});
// Expanded, better
ClipExport.prototype._copyClips = Promise.method(function (filenames) {
return Promise.resolve(filenames)
.map(this._copyClip);
// can chain easier now, error catch, etc
});
Multiple items in an Array
// Compact
ClipExport.prototype._copyClips = Promise.method(function (filenames) {
return Promise.map(filenames, this._copyClip.bind(this), {concurrency: 10});
});
// Expanded, better
ClipExport.prototype._copyClips = Promise.method(function (filenames) {
return Promise.resolve(filenames)
.map(this._copyClip, {concurrency: 10});
// can chain easier now, error catch, etc
});
... with a throttle
Promisifying
Promisify
var Promise = require('bluebird');
// Promisify all methods of a module
var fs = Promise.promisifyAll(require('fs'));
fs.readFileAsync('one').then().catch();
// Promisify a single method
var readFileAsync = Promise.promisify(fs.readFile);
readFileAsync.then().catch();
Spreading Arguments
The spread()
Foo.prototype.one = Promise.method(function () {
return Promise.resolve([
'one',
'two'
])
map(function(val) {
return val + '-lol';
})
.spread(function(arg1, arg2) {
console.log(arg1, arg2);
// prints: "one-lol two-lol"
});
});
All Together
All Together
Foo.prototype.one = Promise.method(function (data) {
this.readAllFilesFor(data)
.bind(this)
.map(this.copyFile)
.then(function(filenames) {
return Promise.all([
this.process(filenames),
this.save(filenames),
this.notify(filenames),
]);
})
.spread(function(fromProcess, fromSave, fromNotify) {
return this.massup(fromProcess, fromSave, fromNotify)
})
.then(this.compress)
.then(this.sign)
.then(this.publish)
.catch(this.onError);
});
<-- filenames is the result of copyfiles()
<-- Parallel Async OPs
<-- spread() is one to one with the array
Thank you
(here is where you applaud)
Thanasis Polychronakis
@thanpolas
Questions
Thanasis Polychronakis
@thanpolas
Promises patterns
By thanpolas
Promises patterns
Useful and practical Promises patterns
- 957