ES6 Promises are great. So far it’s been pretty easy to adjust my thinking from the callback idiom. I’ve found it naturally encourages more modular code, and of course error handling is much clearer.
But a few times I’ve encountered flow situations that don’t seem(?) like they can be readily translated from nodebacks to promises (and perhaps that’s just that, but maybe I’m just blind to the answers). Since promises are agnostic about the next operation (or if there even is one), it seems pretty tough to use Promises with APIs that don’t just take callbacks, but also return them.
The most common example that comes to mind is the ‘done’ callback. It shows up in things like database connections to signify ‘return connection to pool’ but I’ve seen it pop up in plenty of other places, too.
function getSomeStupidConnection(cb) {
var conn = /* ... */;
var iNeedToBeToldWhenIAmDone = function() { /* ... */ };
cb(conn, iNeedToBeToldWhenIAmDone);
}
getSomeStupidConnection(function(conn, done) {
/* ... */
conn.doLotsOfStuff(function(soMuchStuff) {
/* stuff! so much fun! */
/* okay conn go away I’m tired */
done();
});
});
Flow-reversal like this is obviously not something you want to have in your APIs to start with, but it’s out there and you can’t really avoid it sometimes. With callbacks, you can pass the ‘call later’ inner callback to the original ‘outer’ callback. It doesn’t exactly lead to a clean seperation of concerns, but at least it’s quick and simple.
Is there a Promise-based approach suited to situations like this? A way to say, ‘here’s the resolve value -- but when the chain is complete, also do this’? I suspect there’s nothing that perfectly matches what I just described because it isn’t really possible to say a chain is ‘done’, but maybe I’m missing some pattern that gets you close to that without making a mess...
Edit: Based on the feedback so far I've realized that there's simply no way to wrap such an API in true promises, because the promise you return will never be able to tell you anything about any subsequent chained promises that piggyback on it. But you can fake it. The twist is that the result is rather brittle; it must assume that the only then
which needs the connection object is the one which immediately follows. The consumer of the promise would need to understand that it’s a one-time-use connection, which isn’t otherwise obvious. Therefore I don't really recommend it in practice, but for the sake of curiosity here is a solution that hides the done
while behaving as (and ultimately becoming) a promise chain:
/* jshint node: true, esnext: true */
'use strict';
// Assume this comes from an external library. It returns a connection and a
// callback to signal that you are finished with the connection.
function getConnectionExternal(cb) {
let connection = 'Received connection.';
let done = () => console.log('Done was called.');
cb(null, connection, done);
}
// Our promisey wrapper for the above
function getConnection() {
let _done;
let promise = new Promise((resolve, reject) => {
getConnectionExternal((err, connection, done) => {
if (err) return reject(err);
_done = (val) => {
done();
return val;
};
resolve(connection);
});
});
let _then = promise.then.bind(promise);
promise.then = (handler) => _then(handler).then(_done, _done);
return promise;
}
// Test it out
getConnection()
.then(connection => {
console.log(connection);
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Finished using connection!');
resolve('This should be after connection closes.');
}, 200);
});
})
.then(msg => console.log(msg))
.catch(err => console.error(err));
Console prints:
Elaborating on Bergi's solution, this is called the disposer pattern. It exists in many forms in many languages - with
in Python, using
in C# and try(){
with resource in Java. Some languages handle resource in scopes this way natively through destructurs like C#.
The general idea is for a scope to encapsulate the life-time of a value. In your case a database connection. It's a lot tidier than having to call done
in a callback since it's much easier to forget to call done
which leaves an open connection and a resource leak. Synchronously it'd look like:
function scope(cb){
try{
var conn = getConnection(...);
return cb(conn);
} finally {
conn.release();
}
}
The promises version is not too different:
function conn(data){
var _connection;
return getConnection().then(function(connection){
_connection = connection; // keep a reference
return data(_connection); // pass it to the function
}).then(function(val){
// release and forward
_connection.release(); // if release is async - chain
return val;
}, function(err){
_connection.release();
throw err; // forward error
});
});
Which would use:
conn(function(db){
return db.query("SELECT * FROM ...");
}).then(function(result){ // handle result
// connection is released here
});
A way to say, ‘here’s the resolve value -- but when the chain is complete, also do this’?
No, native promises do not provide such a facility. I would go with a resource function that takes a promise-returning callback, the callback does everything (in a chain) that needs to be done while the connection is open. Instead of passing iNeedToBeTold
to the callback, the resource manager function observes the promise and does what needs to be done when it resolves.
function manageConnection(cb) {
return getSomeConnection(…) // get the connections asynchronously - via a promise of course
.then(function(conn) {
function whenDone() {
… // do what needs to be done
return result;
}
var result = cb(conn);
return result.then(whenDone, whenDone);
});
}
manageConnection(function(conn) {
return conn.doLotsOfStuff(soMuch)
.then(function(stuff) {
/* stuff! so much fun! */
});
}).then(…)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With