Skip to content

Instantly share code, notes, and snippets.

@johndgiese
Last active August 29, 2015 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johndgiese/8f0c16e770a5339b4fc7 to your computer and use it in GitHub Desktop.
Save johndgiese/8f0c16e770a5339b4fc7 to your computer and use it in GitHub Desktop.
Group-level transactional support in javascript using promises.
Q = require('Q');
/**
* Store a promise for each `group` of function calls; the existence of a
* promise for a given key indicates a lock on that group, and subsequent
* calls in that group are delayed until that promise is resolved. After the
* last call is complete, the lock (i.e. key) is cleared.
*/
var groupQueues = exports._groupQueus = {};
/**
* Decorate a promise-returning function to ensure that it completes its work
* as a single "transaction" within the specified group.
* @arg {String} - group identifier
* @arg {Function} - work that needs to be done as a transaction within the group
* @return {Function} - decorated function
*
* @example You are writing a game application; when players join a game, you
* have a series of inter-related database calls that must occur; in order to
* ensure the game state is consistent for all players (even if the players are
* joining at the same time), you need to ensure this series of database calls
* completes before the next player starts their join process. Typically, this
* would by performing the database calls in a transaction. In node, while
* using websockets, it is not feasible to use database transactions, because
* you have fewer database connections than you have players connected to
* websockets. To get around this, you can use application-level transactions
* as implemented here. As an added bonus, you can improve performance by
* locking at a group level (as opposed to at a full database level). Note
* that the transaction isolation level created here is equivalent to
* "serializable" in the SQL standard.
*/
exports.inOrderByGroup = function inOrderByGroup(group, func) {
return function() {
var args = arguments;
var deferred = Q.defer();
var queue = groupQueues[group];
if (queue === undefined) {
groupQueues[group] = [deferred];
execute(group, deferred, func, args);
} else {
var prevCall = queue[queue.length - 1].promise;
prevCall.then(function() {
execute(group, deferred, func, args);
});
queue.push(deferred);
}
return deferred.promise;
};
};
function execute(group, deferred, func, args) {
Q.fapply(func, args)
.then(function(val) {
deferred.resolve(val);
}, function(reason) {
deferred.reject(reason);
})
.then(function() {
var queue = groupQueues[group];
queue.shift();
if (queue.length === 0) {
delete groupQueues[group];
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment