Asynchronous Javascript,

what could possibly go wrong?

Call backs are not the enemy.

readFile('test.txt', function(err, contents) {
  if(err) {
    return console.error(err);
  }
  console.log(contents);
});
var filteredList = userList.filter(function(user){
  return user.selected === true;
});// 88

vs.

let filteredList = userList.filter({selected} => selected === true);// 69 | 21.59% reduction.

Passing functions as an argument is a beautiful thing.

Because, when your code looks like this it hurts:

var fs = require('fs')
var path = require('path')
 
module.exports = function (dir, cb) {
  fs.readdir(dir, function (er, files) { // [1]
    if (er) return cb(er)
    var counter = files.length;
    var errored = false;
    var stats = [];
 
    files.forEach(function (file, index) {
      fs.stat(path.join(dir,file), function (er, stat) { // [2]
        if (errored) return;
        if (er) {
          errored = true;
          return cb(er);
        }
        stats[index] = stat; // [3]
 
        if (--counter == 0) { // [4]
          var largest = stats
            .filter(function (stat) { return stat.isFile(); }) // [5]
            .reduce(function (prev, next) { // [6]
              if (prev.size > next.size) return prev;
              return next;
            });
          cb(null, files[stats.indexOf(largest)]); // [7]
        }
      });
    });
  });
};
  1. ​Read all the files inside the directory. 

  2. Gets the stats on each file. A counter is used to track when all I/O has finished. Errored boolean prevents the provided callback from being called more than once if an error occures.

  3. Collect the stats for each file. 

  4. Check to see if all parallel operations have completed

  5. Only grab regular files.

  6. Reduce the list to the largest file.

  7. Pull the file name associated with the state and callback.  

Async.js 

Is this really the abstraction we want?

var fs = require('fs')
var async = require('async')
var path = require('path')
 
module.exports = function (dir, cb) {
  async.waterfall([ // [1]
    function (next) {
      fs.readdir(dir, next)
    },
    function (files, next) {
      var paths = 
       files.map(function (file) { return path.join(dir,file) })
      async.map(paths, fs.stat, function (er, stats) { // [2]
        next(er, files, stats)
      })
    },
    function (files, stats, next) {
      var largest = stats
        .filter(function (stat) { return stat.isFile() })
        .reduce(function (prev, next) {
          if (prev.size > next.size) return prev
          return next
        })
        next(null, files[stats.indexOf(largest)])
    }
  ], cb) // [3]
}
  1. Async.waterfall: provides a series flow of execution, where data from one operation can be passed to the next function in the series. 
  2. Async.map lets us run fs.stat over a set of paths in parallel 
  3. The callback function will be called either after the last step or if any error has occured along the way. It will be called only once. 

Probably Not.

Continuation Passing Style

  • No extra abstractions on top of your code
  • No extra dependancies in your project.
  • Most performant in terms of memory usage and processing time. 

Pro's

Con's

  • Harder to Read/Reason About
  • Side Affects are less obvious.
  • Imperative code sucks.
  • Performance in your software comes at the expense of performance for your developers.
  • Error propagation is a manual effort.
  • Process execution chain isn't declared. 

Promises

How Do they work?

Promise.then

The then() method returns a promise. It takes two arguments: callback functions for the success and failure cases of the Promise.
p.then(onFulfilled, onRejected);

p.then(function(value) {
   // fulfillment
  }, function(reason) {
  // rejection
});
var p1 = new Promise(function(resolve, reject) {
  resolve("Success!");
});

p1.then(function(value) {
    console.log(value); // Success!
});

Promise.catch

The catch() method returns a Promise and deals with rejected cases only. It behaves the same as calling:
Promise.prototype.then(undefined, onRejected)
var p1 = new Promise(function(resolve, reject) {
  reject("This is why we can't have nice things!");
});

p1.catch(function(error) {
  console.log(error); // This is why we can't have nice things! 
});

Promise.all

The Promise.all(iterable) method returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first rejected promise.
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, "foo");
}); 

Promise.all([p1, p2, p3]).then(function(values) { 
  console.log(values); // [3, 1337, "foo"] 
});

What is the Point?

  • Correspondence between synchronous and asynchronous functions
  • The point of promises is to give us back functional composition and error bubbling in the async world. 
getTweetsFor("domenic") // promise-returning async function
  .then(function (tweets) {
    // promise-returning async function
    return expandUrlUsingTwitterApi(parseTweetsForUrls(tweets)[0]); 
  })
  .then(doHttpRequest) // promise-returning async function
  .then(function (responseBody) {
    console.log("Most recent link text:", responseBody);
  }).catch(function(error) {
    console.error("Error with the twitterverse:", error);
  });


try {
  var tweets = getTweetsFor("domenic"); // blocking
  // blocking x 2
  var responseBody = doHttpRequest(expandUrlUsingTwitterApi(parseTweetsForUrls(tweets)[0])); 
  console.log("Most recent link text:", responseBody);
} catch (error) {
  console.error("Error with the twitterverse: ", error);
}

What's that mean for our codez?

var fs = require('fs');
var path = require('path');
var Promise = require('bluebird');
var fs_readdir = Promise.promisify(fs.readdir); // [1]
var fs_stat =  Promise.promisify(fs.stat);
 
module.exports = function (dir) {
  return fs_readdir(dir)
    .then(function (files) {
      var promises = files.map(function (file) {
        return fs_stat(path.join(dir,file));
      })
      return Promise.all(promises).then(function (stats) { // [2]
        return [files, stats]; // [3]
      })
    })
    .then(function (data) { // [4]
      var files = data[0];
      var stats = data[1];
      var largest = stats
        .filter(function (stat) { return stat.isFile() })
        .reduce(function (prev, next) {
        if (prev.size > next.size) return prev;
          return next;
        })
      return files[stats.indexOf(largest)];
    })
} //830
  1. Node core isn't promise aware, so we make it so.
  2. Promise.all runs all the stat calls in parallel.
  3. we return files and stats out of our fs_readddir function.
  4. We access the fullfilled value from the fs_readdir callback.

What if we tried to go declarative/FP and used the new(old) shiny.

const fs = require('fs');
const path = require('path');
const Promise = require('bluebird');
const fs_readdir = Promise.promisify(fs.readdir);
const fs_stat = Promise.promisify(fs.stat);

const getFileStats = filesList => filesList.map(file => fs_stat(path.join(dir,file)));

const findLargestFile = statsList => 
  statsList
  .filter(stat => stat.isFile())
  .reduce((prev, next) => prev.size > next.size ? prev: next);

module.exports = dir =>
  fs_readdir(dir).then(filesList => 
    Promise.all(getFileStats(filesList)).then(statsList => [filesList, statsList]))
    .then([filesList, statsList] => filesList[stats.indexOf(findLargestFile(statsList))]);
// 657 | 24.91% reduction

Promises

Pros:

  • Allows for Composition of Behavior.
  • Promises are a Standard feature of Javascript
  • Concise Declarative syntax
  • Very easy to reason about complex behaviour
  • Error handling is automatic and handles explicit and implicit errors. 
  • Performance slightly worse than Callbacks
  • Cannot be cancelled
  • Read only
  • Not Lazy
  • Cannot give updates before final resolution

Cons:

What's next?
You decide!

  1. Common Promise Patterns for solving problems like a boss.
  2. Common Promise Anti Patterns that will result in numerous comments on your pull requests.
  3. Working with legacy code and remaining productive.
  4. Introduction to Functional Programming

deck

By bobbiebarker

deck

  • 843