43

I've read about the promise disposer pattern in several places but I can't figure out what it is. It was suggested to me to use it in code that looks like:

function getDb(){
    return myDbDriver.getConnection();
}

var users = getDb().then(function(conn){
     return conn.query("SELECT name FROM users").finally(function(users){
         conn.release();
     });
});

What's the promise disposer pattern and how does it apply here?


Note - in native promises, I shim .finally as "add both rejection and fulfillment handlers that return the value but perform an action". I'm using bluebird in this case if it matters.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504

1 Answers1

52

The issue with your code

The problem with the above approach is that if you forget releasing the connection after every single time you perform getDb you have a resource leak that might freeze your app eventually when it runs out of the resource you're leaking.

You might, in one place do:

var users = getDb().then(function(conn){
     return conn.query("SELECT name FROM users");
});

Which will leak a database connection that was never closed.


The disposer pattern

The disposer pattern is a way to couple a scope of code with owning the resource. By binding the resource to a scope we make sure it is always released when we're done with it and we can't easily forget to release it. It is similar to using in C#, with in Python and try-with-resource in Java as well as RAII in C++.

It looks like:

 withResource(function(resource){
     return fnThatDoesWorkWithResource(resource); // returns a promise
 }).then(function(result){
    // resource disposed here
 });

Applying it here

If we wrote our code as:

function withDb(work){
    var _db;
    return myDbDriver.getConnection().then(function(db){
        _db = db; // keep reference 
        return work(db); // perform work on db
    }).finally(function(){
        if (_db)
            _db.release();
    });
}

We could write our above code as:

 withDb(function(conn){
     return conn.query("SELECT name FROM users");
 }).then(function(users){
     // connection released here
 });

Examples of users of the disposer pattern are sequelize and knex (bookshelf's query builder). It's also possible to use it for simpler things like hiding a loader when all AJAX requests completed for instance.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 1
    Do you have an example of `Promise.using` usage, to compare with your disposer pattern? – Florian Margaine Mar 10 '15 at 07:55
  • Isn't this missing an `if (_db)`? If `getConnection` fails, the `withDb()` would reject with a "cannot call release on undefined" error currently. – Bergi Jun 29 '15 at 16:25
  • @Bergi if `getConnection` fails you'd want your app to crash and burn (since the _connection pool_ and _database connection_ is failing and not the operation on the database) but I agree that should be explicit - feel free to edit or suggest an edit. – Benjamin Gruenbaum Jun 29 '15 at 16:26
  • 1
    Yeah, you'd probably want to crash, but with the right exception that states the connectivity issue :-) – Bergi Jun 29 '15 at 16:31
  • Is this safe? Say two `withDb()` calls were made in quick succession. Would the two connections be completely independent?. If not, then you might need a mechanism by which either connection was released only when both requests were satisfied. – Roamer-1888 Jul 01 '15 at 02:47
  • @Roamer-1888 why would they be dependent they are two different connections from the connection pool – Benjamin Gruenbaum Jul 01 '15 at 11:31
  • @BenjaminGruenbaum, if you're saying they *are* independent, then that's fine - my concern disappears. – Roamer-1888 Jul 01 '15 at 11:44
  • @Roamer-1888 yes, they are completely independent, if they're not then that would have been a very serious problem :) – Benjamin Gruenbaum Jul 01 '15 at 12:03