Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Sweet Patterns for Promises

Sweet Patterns for Promises

A collection of Promises/A+ usage patterns

Cbb9cafe03e785d80a9aa973c5e43c44?s=128

Thanos Polychronakis

January 29, 2015
Tweet

Transcript

  1. Sweet Patterns for Sweet Patterns for Promises Promises

  2. Promises/A+ Promises/A+ ok? ok?

  3. One Choice One Choice Bluebird Bluebird /petkaantonov/bluebird

  4. It's the FASTEST It's the FASTEST and robust and robust

    Look it up! Look it up!
  5. What are Promises? What are Promises?

  6. Manage Asynchronicity Manage Asynchronicity Single callback guarantee Robust error handling

    Composability
  7. Single Callback Guarantee Single Callback Guarantee entity.create() .then(function(udo) { //

    once for either outcome }) .catch(function(error) { // once for either outcome });
  8. 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) {});
  9. Composability Composability entity.create() .then(entity.read) .then(entity.parseForApi) .then(entity.process) .catch(entity.onError);

  10. Creating Promises Creating Promises 2 ways 2 ways

  11. 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); } }); }); };
  12. 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(); });
  13. 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); } });
  14. Error Handling with method() Error Handling with method() Throw away

    Throw away ClipEntity.prototype.showVideos = Promise.method(function() { if(someCondition) { throw new Error('ops'); } });
  15. REMEMBER REMEMBER returning a Promise is returning a Promise is

    CRITICAL! CRITICAL! But you may return any value as well
  16. Promises will only Promises will only return one argument return

    one argument
  17. 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 });
  18. Working with Working with Promises Promises

  19. Consuming a Promise Consuming a Promise entity.create({name: 'thanasis'}) .then(function(udo) {

    udo.name === 'thanasis'; // true }) .catch(function(error) { // deal with error. });
  20. 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() !!
  21. 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
  22. 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!
  23. 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); };
  24. 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
  25. 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 });
  26. 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
  27. Promisifying Promisifying

  28. 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();
  29. Spreading Spreading Arguments Arguments

  30. 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" }); });
  31. All Together All Together

  32. 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
  33. Thank you (here is where you applaud) Thanasis Polychronakis @thanpolas

  34. Questions Thanasis Polychronakis @thanpolas