| /* |
| * Utilities: A classic collection of JavaScript utilities |
| * Copyright 2112 Matthew Eernisse (mde@fleegix.org) |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| var async = {}; |
| |
| /* |
| AsyncChain -- performs a list of asynchronous calls in a desired order. |
| Optional "last" method can be set to run after all the items in the |
| chain have completed. |
| |
| // Example usage |
| var asyncChain = new async.AsyncChain([ |
| { |
| func: app.trainToBangkok, |
| args: [geddy, neil, alex], |
| callback: null, // No callback for this action |
| }, |
| { |
| func: fs.readdir, |
| args: [geddy.config.dirname + '/thailand/express'], |
| callback: function (err, result) { |
| if (err) { |
| // Bail out completely |
| arguments.callee.chain.abort(); |
| } |
| else if (result.theBest) { |
| // Don't run the next item in the chain; go directly |
| // to the 'last' method. |
| arguments.callee.chain.shortCircuit(); |
| } |
| else { |
| // Otherwise do some other stuff and |
| // then go to the next link |
| } |
| } |
| }, |
| { |
| func: child_process.exec, |
| args: ['ls ./'], |
| callback: this.hitTheStops |
| } |
| ]); |
| |
| // Function to exec after all the links in the chain finish |
| asyncChain.last = function () { // Do some final stuff }; |
| |
| // Start the async-chain |
| asyncChain.run(); |
| |
| */ |
| async.execNonBlocking = function (func) { |
| if (typeof process != 'undefined' && typeof process.nextTick == 'function') { |
| process.nextTick(func); |
| } |
| else { |
| setTimeout(func, 0); |
| } |
| }; |
| |
| async.AsyncBase = new (function () { |
| |
| this.init = function (chain) { |
| var item; |
| this.chain = []; |
| this.currentItem = null; |
| this.shortCircuited = false; |
| this.shortCircuitedArgs = undefined; |
| this.aborted = false; |
| |
| for (var i = 0; i < chain.length; i++) { |
| item = chain[i]; |
| this.chain.push(new async.AsyncCall( |
| item.func, item.args, item.callback, item.context)); |
| } |
| }; |
| |
| this.runItem = function (item) { |
| // Reference to the current item in the chain -- used |
| // to look up the callback to execute with execCallback |
| this.currentItem = item; |
| // Scopage |
| var _this = this; |
| // Pass the arguments passed to the current async call |
| // to the callback executor, execute it in the correct scope |
| var executor = function () { |
| _this.execCallback.apply(_this, arguments); |
| }; |
| // Append the callback executor to the end of the arguments |
| // Node helpfully always has the callback func last |
| var args = item.args.concat(executor); |
| var func = item.func; |
| // Run the async call |
| func.apply(item.context, args); |
| }; |
| |
| this.next = function () { |
| if (this.chain.length) { |
| this.runItem(this.chain.shift()); |
| } |
| else { |
| this.last(); |
| } |
| }; |
| |
| this.execCallback = function () { |
| // Look up the callback, if any, specified for this async call |
| var callback = this.currentItem.callback; |
| // If there's a callback, do it |
| if (callback && typeof callback == 'function') { |
| // Allow access to the chain from inside the callback by setting |
| // callback.chain = this, and then using arguments.callee.chain |
| callback.chain = this; |
| callback.apply(this.currentItem.context, arguments); |
| } |
| |
| this.currentItem.finished = true; |
| |
| // If one of the async callbacks called chain.shortCircuit, |
| // skip to the 'last' function for the chain |
| if (this.shortCircuited) { |
| this.last.apply(null, this.shortCircuitedArgs); |
| } |
| // If one of the async callbacks called chain.abort, |
| // bail completely out |
| else if (this.aborted) { |
| return; |
| } |
| // Otherwise run the next item, if any, in the chain |
| // Let's try not to block if we don't have to |
| else { |
| // Scopage |
| var _this = this; |
| async.execNonBlocking(function () { _this.next.call(_this); }); |
| } |
| } |
| |
| // Short-circuit the chain, jump straight to the 'last' function |
| this.shortCircuit = function () { |
| this.shortCircuitedArgs = arguments; |
| this.shortCircuited = true; |
| } |
| |
| // Stop execution of the chain, bail completely out |
| this.abort = function () { |
| this.aborted = true; |
| } |
| |
| // Kick off the chain by grabbing the first item and running it |
| this.run = this.next; |
| |
| // Function to run when the chain is done -- default is a no-op |
| this.last = function () {}; |
| |
| })(); |
| |
| async.AsyncChain = function (chain) { |
| this.init(chain); |
| }; |
| |
| async.AsyncChain.prototype = async.AsyncBase; |
| |
| async.AsyncGroup = function (group) { |
| var item; |
| var callback; |
| var args; |
| |
| this.group = []; |
| this.outstandingCount = 0; |
| |
| for (var i = 0; i < group.length; i++) { |
| item = group[i]; |
| this.group.push(new async.AsyncCall( |
| item.func, item.args, item.callback, item.context)); |
| this.outstandingCount++; |
| } |
| |
| }; |
| |
| /* |
| Simpler way to group async calls -- doesn't ensure completion order, |
| but still has a "last" method called when the entire group of calls |
| have completed. |
| */ |
| async.AsyncGroup.prototype = new function () { |
| this.run = function () { |
| var _this = this |
| , group = this.group |
| , item |
| , createItem = function (item, args) { |
| return function () { |
| item.func.apply(item.context, args); |
| }; |
| } |
| , createCallback = function (item) { |
| return function () { |
| if (item.callback) { |
| item.callback.apply(null, arguments); |
| } |
| _this.finish.call(_this); |
| } |
| }; |
| |
| for (var i = 0; i < group.length; i++) { |
| item = group[i]; |
| callback = createCallback(item); |
| args = item.args.concat(callback); |
| // Run the async call |
| async.execNonBlocking(createItem(item, args)); |
| } |
| }; |
| |
| this.finish = function () { |
| this.outstandingCount--; |
| if (!this.outstandingCount) { |
| this.last(); |
| }; |
| }; |
| |
| this.last = function () {}; |
| |
| }; |
| |
| var _createSimpleAsyncCall = function (func, context) { |
| return { |
| func: func |
| , args: [] |
| , callback: function () {} |
| , context: context |
| }; |
| }; |
| |
| async.SimpleAsyncChain = function (funcs, context) { |
| chain = []; |
| for (var i = 0, ii = funcs.length; i < ii; i++) { |
| chain.push(_createSimpleAsyncCall(funcs[i], context)); |
| } |
| this.init(chain); |
| }; |
| |
| async.SimpleAsyncChain.prototype = async.AsyncBase; |
| |
| async.AsyncCall = function (func, args, callback, context) { |
| this.func = func; |
| this.args = args; |
| this.callback = callback || null; |
| this.context = context || null; |
| }; |
| |
| async.Initializer = function (steps, callback) { |
| var self = this; |
| this.steps = {}; |
| this.callback = callback; |
| // Create an object-literal of the steps to tick off |
| steps.forEach(function (step) { |
| self.steps[step] = false; |
| }); |
| }; |
| |
| async.Initializer.prototype = new (function () { |
| this.complete = function (step) { |
| var steps = this.steps; |
| // Tick this step off |
| steps[step] = true; |
| // Iterate the steps -- if any are not done, bail out |
| for (var p in steps) { |
| if (!steps[p]) { |
| return false; |
| } |
| } |
| // If all steps are done, run the callback |
| this.callback(); |
| }; |
| })(); |
| |
| module.exports = async; |
| |