Slide 1

Slide 1 text

Sweet Patterns for Sweet Patterns for Promises Promises

Slide 2

Slide 2 text

Promises/A+ Promises/A+ ok? ok?

Slide 3

Slide 3 text

One Choice One Choice Bluebird Bluebird /petkaantonov/bluebird

Slide 4

Slide 4 text

It's the FASTEST It's the FASTEST and robust and robust Look it up! Look it up!

Slide 5

Slide 5 text

What are Promises? What are Promises?

Slide 6

Slide 6 text

Manage Asynchronicity Manage Asynchronicity Single callback guarantee Robust error handling Composability

Slide 7

Slide 7 text

Single Callback Guarantee Single Callback Guarantee entity.create() .then(function(udo) { // once for either outcome }) .catch(function(error) { // once for either outcome });

Slide 8

Slide 8 text

Robust Error Handling 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) {});

Slide 9

Slide 9 text

Composability Composability entity.create() .then(entity.read) .then(entity.parseForApi) .then(entity.process) .catch(entity.onError);

Slide 10

Slide 10 text

Creating Promises Creating Promises 2 ways 2 ways

Slide 11

Slide 11 text

Creating a Novel Promise Creating a Novel Promise Typically interfacing with vanilla callbacks 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); } }); }); };

Slide 12

Slide 12 text

Interfacing with Promises Interfacing with Promises When your interfaces are all returning promises When your interfaces are all returning promises var Promise = require('bluebird'); /* ... */ ClipEntity.prototype.showVideos = Promise.method(function() { return this.fetchVideos(); });

Slide 13

Slide 13 text

Error Handling with new Error Handling with new Always use reject() Always use reject() return new Promise(function(resolve, reject) { if (someCondition) { var err = new Error('Not good'); reject(err); } });

Slide 14

Slide 14 text

Error Handling with method() Error Handling with method() Throw away Throw away ClipEntity.prototype.showVideos = Promise.method(function() { if(someCondition) { throw new Error('ops'); } });

Slide 15

Slide 15 text

REMEMBER REMEMBER returning a Promise is returning a Promise is CRITICAL! CRITICAL! But you may return any value as well

Slide 16

Slide 16 text

Promises will only Promises will only return one argument return one argument

Slide 17

Slide 17 text

Using method() Using method() Anything you return will be the resolving value 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 });

Slide 18

Slide 18 text

Working with Working with Promises Promises

Slide 19

Slide 19 text

Consuming a Promise Consuming a Promise entity.create({name: 'thanasis'}) .then(function(udo) { udo.name === 'thanasis'; // true }) .catch(function(error) { // deal with error. });

Slide 20

Slide 20 text

Composing 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() !!

Slide 21

Slide 21 text

Error Chaining 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: throw error; }) .then(function() { throw err; }); }); The Rollback <--- From clipEnt.delete() <--- From Parent

Slide 22

Slide 22 text

Returning Custom Value 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!

Slide 23

Slide 23 text

Running in Parallel 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); };

Slide 24

Slide 24 text

Running in Parallel 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

Slide 25

Slide 25 text

Applying an Array to a Promise 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 });

Slide 26

Slide 26 text

Multiple items in an Array 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

Slide 27

Slide 27 text

Promisifying Promisifying

Slide 28

Slide 28 text

Promisify 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();

Slide 29

Slide 29 text

Spreading Spreading Arguments Arguments

Slide 30

Slide 30 text

The spread() 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" }); });

Slide 31

Slide 31 text

All Together All Together

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

Thank you (here is where you applaud) Thanasis Polychronakis @thanpolas

Slide 34

Slide 34 text

Questions Thanasis Polychronakis @thanpolas