Promises in Javascript – An alternative approach to handling asynchronous function calls – Rewriting the fs.stat example using Q / Promises



Promises in Javascript – An alternative approach to handling asynchronous function calls – Rewriting the fs.stat example using Q / Promises

0 0


promises-presentation

Rushing a presentation on Promises in Javascript

On Github masotime / promises-presentation

Promises in Javascript

An alternative approach to handling asynchronous function calls

by Benjamin Goh, made using reveal.js

Who am I?

Benjamin Goh

  • From Singapore - UIE-SDC
  • Using Kraken on Risk Projects
  • UIE 2 - No "Director", "Architect" or "MTS" in job title...

Javascript is synchronous.

You write from A-Z, and expect execution from A-Z

console.log('Hello');
while(true);
console.log('world'); // this will never be reached

Callback Mechanic

Events versus threads - onclick, onmouseover

document.getElementById('mybutton').onclick = function(e) {
	 alert('hello world'); 
};

Pass a function as an argument

function nonBlocking(arg, callback) {
  setTimeout(function() { // "spawn thread"
    try {
      var data = readFile(arg); // blocking code
      callback(null, data); // event fired, data returned
    } catch(e) {
      callback(e); // event fired, error occurred
    }
  }, 100);
}

console.log('Hello');
nonBlocking('pineapple.txt', function(result) { 
  console.log(result); 
}); // this doesn't block
console.log('World');

Node.JS Conventions

Async function

function async(...args, callback) { // LAST ARG MUST BE CALLBACK
	// synchronous code
	if (success) {
		callback(null, result);
	} else {
		callback(err, result); // if any
	}
}

Callback handler

function callback(error, result) { // FIRST ARG MUST BE ERROR
	if (error) {
		// handle error
	} else {
		// so something with the result
	}
}

On the side - array processing

map

Map each array element through a function, return a new array

var numbers = [1,2,3,4,5];
var inc = function(x) { return x+1 };
var newNumbers = numbers.map(inc); // newNumbers = [2,3,4,5,6]

reduce

Go through each array element, reduce it to a single result

var adder = function(sum, current) { return sum + current };
var sum = numbers.reduce(adder); // sum = 1 + 2 + 3 + 4 + 5 = 15

Parallel Example

  • Finding the sizes of 3 files in parallel
  • Straightforward as the requirements are not complicated...

Series Example

  • Joining the contents of 3 files
  • More difficult - order is important...

caolan’s async to the rescue

  • async provides flow control utilities.
  • Code becomes compact and readable
  • Good for streamlining simple asynchronous requirements...

More complex scenarios

Source: Promises are Node's Biggest Missed Opportunity
  • Using the result of a async function in more than one place...
  • You can eventually form a "best" answer, but it takes some effort to get there...

What if you could code

“synchronously”?

var files = ['data/data1', 'data/data2', 'data/data3'],
    filestats;
  
try {
    filestats = files.map(fs.stat);
    useFileStats(filestats);
} catch (e) {
    console.error(e);
}

useThirdFileSize(filestats[2].size);

Introducing Promises

Promises are like IOUs

They represent a “future value” which I can’t give right away because I need to work on it.

Callbacks are... callbacks

Callbacks don’t give you anything, you’ll just have to wait for them to call back before you can do something.

Compare and contrast

Promise

function doPerformanceReview() {
  var deferred = Q.defer();
  setTimeout(function() {
    deferred.resolve('My review');
  }, 2000);
  return deferred.promise; // RETURNS A PROMISE
}

Callback

function doPerformanceReview(callback) {
  setTimeout(function() {
    callback(null, 'My review');
  }, 2000); // RETURNS NOTHING
}

Promise Frameworks

Promises in Javascript usually implement what is known as the Promises/A+ standard

Q is the most popular framework, but there are many others, like Bluebird, rsvp, etc.

Rewriting the fs.stat example using Q / Promises

Is it really better?

The syntax isn’t exactly shorter or clearer

Actual

var statPromises = files.map(function(file) { return fs_stat(file); }); // array of promises

Q.all(statPromises).then(useFileStats)
    .done(undefined, function(err) {
        console.error('problem trying to stat all files', err); // localized error handling
    });

statPromises[2].get('stat')
    .then(useThirdFileSize).done();

Ideal

var files = ['data/data1', 'data/data2', 'data/data3'],
    
try {
    useFileStats(files.map(fs.stat));
} catch (e) {
    console.error(e);
}

useThirdFileSize(fs.stat(files[2]).size);

The difference is mainly in the way you think

async is good for flow control, but it doesn’t help you think in terms of dependencies.

Let's try to model a real world example using Q.

Simple UIE workflow

Pros

  • Allow you to think in terms of dependencies
  • Automatically optimized based on dependencies
  • Easier error handling

Cons

  • No advantage in terms of code length versus async
  • Callbacks are still the gold standard
  • Syntax is hard to get used to

My recommendation? Use it when you have data from multiple sources that must be combined in different ways.

ES6 Generators

yield and function*

  • Generators do not directly enable synchronous-like programming
  • yield provides non-blocking functionality
  • Callback handling is done in "resume managers" outside of the generator function....

The end

Q & A?