Last active
August 29, 2015 14:04
-
-
Save johndgiese/8f0c16e770a5339b4fc7 to your computer and use it in GitHub Desktop.
Group-level transactional support in javascript using promises.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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