Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Promise with q framework and the callback pattern in Node.js?

Even if well documented q framework is quite hard to understand if you are programming with Node.js for a few days. But I like to learn about it!

var Q  = require('q');
var fs = require('fs');

// Make the promise manually (returns a value or throws an error)
var read1 = fs.readFile(fname, enc, function (err, data) {
    if(err) throw err;

    return data;
});

// Convenient helper for node, equivalent to read1?
var read2 = Q.nfbind(fs.readFile);

// Uh?!
var read3 = function (fname, enc) {
    var deferred = Q.defer();

    fs.readFile(fname, enc, function (error, text) {
        if (error) {
            deferred.reject(new Error(error));
        } else {
            deferred.resolve(text);
        }

        return deferred.promise;
    });
};


// Execute
Q.fncall(read1).then(function (data) {}, function (err) {}).done();

Are read1, read2 and read3 equivalent? Can I use Q.nfbind every time the last parameter of a function accept a callback in the style of function (err, value)?

like image 913
gremo Avatar asked Dec 21 '22 09:12

gremo


1 Answers

You have a few errors in your examples.

read1

This is not 'make a promise manually', this is just making a normal asynchronous call. In your code, you call readFile immediately, so read1 would be the return value of readFile which is undefined. To get a behavior similar to read2 and read3 you would need to do something like this:

var read1 = function(fname, env, success, error){
  fs.readFile(fname, enc, function (err, data) {
    // Throwing here would just crash your application.
    if(err) error(err);

    // Returning from inside 'readFile' does nothing, instead you use a callback.
    else success(data);
  });
};

read2

// Not equivalent to read1 because of the notes above,
// Equivalent to read3, with the fixes I mention below.
var read2 = Q.nfbind(fs.readFile);

read3

var read3 = function (fname, enc) {
    var deferred = Q.defer();

    fs.readFile(fname, enc, function (error, text) {
        if (error) {
            // 'error' is already an error object, you don't need 'new Error()'.
            deferred.reject(error);
        } else {
            deferred.resolve(text);
        }

        // HERE: Again returning a value from 'readFile' does not make sense.
        return deferred.promise;
    });

    // INSTEAD: Return here, so you can access the promise when you call 'read3'.
    return deferred.promise.
};

You can indeed use nfbind on anything that takes a callback as the last parameter. With my comments, read2 and read3 accomplish the same goal, which is to create a function that will take a filename and encoding, and return a promise object.

For those, you can do this:

read2('file.txt', 'utf8').then(function (data) {}, function (err) {}).done();
read3('file.txt', 'utf8').then(function (data) {}, function (err) {}).done();

For read1, you would call it like this:

read1('file.txt', 'utf8', function (data) {}, function (err) {});

Update

Standard promises have evolved a bit since this was answered, and if you are leaning toward read3, I'd recommend doing the following:

var read4 = function (fname, enc) {
    return Q.promise(function(resolve, reject){
        fs.readFile(fname, enc, function (error, text) {
            if (error) {
                // 'error' is already an error object, you don't need 'new Error()'.
                reject(error);
            } else {
                resolve(text);
            }
        });
    });
};

This is more in line with standard ES6 promises, and with bluebird, so you'll have an easier time with the code moving forward. Using the method mentioned in read3 also introduces the possibility of synchronously throwing exceptions instead of capturing them in the promise chain, which is usually undesirable. See the deferred antipattern.

like image 143
loganfsmyth Avatar answered Dec 24 '22 00:12

loganfsmyth