Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generator functions in express with bluebird and co

I'm trying out some of the harmony features in node 0.12, in particular trying out the new generators feature. I'm doing this with co (v4), bluebird and express (v4), something like this:

 // ...
var fs = bluebird.promisifyAll(require('fs'));

// ...
app.post('/test', co.wrap(function* (req, res, next) {
    var contents = yield fs.readFileAsync('/etc/hosts', 'utf8');
    return res.send(contents);
}));
// ...

According to its documentation, co.wrap returns a normal function that returns a promise from the given generator function.

This is working fine so far, but what I'm not sure is if a) I'm leaking memory by not 'waiting' for the returned promise's result and b) If I might lose an exception thrown in my generator function, or one of the modules used by it.

Is this a good approach? Do you see anything wrong with it?.

like image 704
Matt Avatar asked Mar 21 '15 18:03

Matt


1 Answers

The problem with your approach is that if your generator function will throw some exception, it will not be passed to next middleware. So you will lose it. You can use bluebird's Promise.coroutine function to implement your own simple co wrapper, which will be working well in express:

// module: ../helpers/co.js
var Promise = require('bluebird');
var co      = Promise.coroutine;

module.exports = function(gen) {
    var coGen = co(gen);

    function handle_error(err, req, res, next) {
        return coGen.apply(this, arguments).catch(next);
    }

    function handle_request(req, res, next) {
        return coGen.apply(this, arguments).catch(next);
    }

    return gen.length > 3 ? handle_error : handle_request;
};

UPD: I have changed the realization a little. Now it takes into account the number or arguments passed into the generator: if > 3 then error handler will be used, otherwise - request handler. It's important for express (look in the source code here and here)

Now you can use it in your code:

// module: your/router.js

// ...
var co = require('../helpers/co');    
var fs = bluebird.promisifyAll(require('fs'));

// ...
app.post('/test', co(function* (req, res, next) {
    var contents = yield fs.readFileAsync('/etc/hosts', 'utf8');
    return res.send(contents);
}));
// ...

UPD This is solution for express with version <= 4.x. Most likely express 5.x will support promises, so you'll can use just bluebird's Promis.coroutine without any fancy wrappers:

// module: your/router.js

// ...
var fs = bluebird.promisifyAll(require('fs'));
var co = bluebird.coroutine;    

// ...
app.post('/test', co(function*(req, res, next) {
    var contents = yield fs.readFileAsync('/etc/hosts', 'utf8');
    return res.send(contents);
}));
// ...
like image 179
alexpods Avatar answered Sep 27 '22 20:09

alexpods