q

Promises Pattern in Node.js with Q

A promise is an abstraction for asynchronous programming. It’s an object that proxies for the return value or the exception thrown by a function that has to do some asynchronous processing. — Kris Kowal on JSJ

A promise is defined as an object that has a function as the value for the property then:

then(fulfilledHandler, errorHandler, progressHandler)

Adds a fulfilledHandler, errorHandler, and progressHandler to be called for completion of a promise. The fulfilledHandler is called when the promise is fulfilled. The errorHandler is called when a promise fails. The progressHandler is called for progress events. All arguments are optional and non-function values are ignored. The progressHandler is not only an optional argument, but progress events are purely optional. Promise implementors are not required to ever call a progressHandler (the progressHandler may be ignored), this parameter exists so that implementors may call it if they have progress events to report.

This function should return a new promise that is fulfilled when the given fulfilledHandler or errorHandler callback is finished. This allows promise operations to be chained together. The value returned from the callback handler is the fulfillment value for the returned promise. If the callback throws an error, the returned promise will be moved to failed state.

Creating raw promises

687474703a2f2f692e696d6775722e636f6d2f444347486466352e706e67

Manually create a promise using Q.defer. Let’s say we wanted to manually wrap fs.readFile to be promise aware :

function fs_readFile (file, encoding) {
    var deferred = Q.defer();
    fs.readFile(file, function (err, data) {
        if (err) deferred.reject(err) ;// rejects the promise with `er` as the reason
        else deferred.resolve(data); // fulfills the promise with `data` as the value
    });
    return deferred.promise; // the promise is returned
}

Making APIs that support both callbacks and promises

You can also make APIs that provide both a promise and callback interface. For example, let’s turn fs.readFile into an API that supports both callbacks and promises:

function fs_readFile (file, encoding, callback) {
 var deferred = Q.defer()
 fs.readFile(function (err, data) {
 if (err) deferred.reject(err) // rejects the promise with `er` as the reason
 else deferred.resolve(data) // fulfills the promise with `data` as the value
 })
 return deferred.promise.nodeify(callback) // the promise is returned
}

If a callback is provided, it will be called with the standard Node style (err, result) arguments when the promise is rejected or resolved.

fs_readFile('myfile.txt', 'utf8', function (er, data) {
    // ...
})

Catch Errors

(courtesy of – http://stackoverflow.com/)

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

It seems that the example is suggesting the following as the correctly way.

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

What’s the difference?

The .then() call will return a promise that will be rejected in case the callback throws an error. This means, when your success logger fails, the error would be passed to the following .catch() callback, but not to the fail callback that goes alongside with success.

Here’s a control flow diagram:

wX5mr WAcpP

To express it in synchronous code:

// some_promise_call().then(logger.log, logger.log)
try {
    var results = some_call();
} catch(e) {
    logger.log(e);
}
if (results) // yes, this odd
    logger.log(results);

The second log (which is like the first argument to .then()) will be executed in case that no exception happened.

// some_promise_call().then(logger.log).catch(logger.log)
try {
    var results = some_call();
    logger.log(results);
} catch(e) {
    logger.log(e);
}

The catch logger will also handle exceptions from the success logger call.

So much for the difference.

I don’t quite understand its explanation as for the try and catch

The argument is that usually you want to catch errors in every step of the processing, and that you shouldn’t use it in chains. The expectation is that you only have one final handler which handles all errors – while, when you use the “antipattern”, errors in some of the then-callbacks are not handled.

However, this pattern is actually very useful: When you want to handle errors that happened in exactly this step, and you want to do something entirely different when no error happened – i.e. when the error is unrecoverable. Be aware that this is branching your control flow. Of course, this is sometimes desired.


What’s wrong with this the following?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

That you had to repeat your callback. You rather want

some_promise_call()
   .catch(function(e) {
       return e; // it's OK, we'll just log it
   })
   .done(function(res) {
       logger.log(res);
   });

You also might consider using .finally() for this.

 Sample Q code:

Example 1

function fs_readFile (file, encoding) {
    var deferred = Q.defer();
    fs.readFile(file, function (err, data) {
        if (err) deferred.reject(err) ;// rejects the promise with `er` as the reason
        else deferred.resolve(data); // fulfills the promise with `data` as the value
    });
    return deferred.promise; // the promise is returned
}

var allPromise = Q.all([ fs_readFile('myfile1.txt'), fs_readFile('myfile2.txt'), callAPI() ]);

allPromise.then(console.log, console.error);

Example 2: finds the largest file within a directory

findLargest.js

var fs = require('fs')
var path = require('path')
var Q = require('q')
var fs_readdir = Q.denodeify(fs.readdir) // [1]
var fs_stat = Q.denodeify(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 Q.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)]
 })
}
var findLargest = require('./findLargest')
findLargest('./path/to/dir')
 .then(function (er, filename) {
 console.log('largest file was:', filename)
 })
 .catch(console.error)

How to chaining promises (Q) with multiple arguments

http://stackoverflow.com/ Chaining promises with multiple arguments

Tags: , , ,