| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- /*
- * Utilities: A classic collection of JavaScript utilities
- * Copyright 2112 Matthew Eernisse ([email protected])
- *
- * 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.addItem(item);
- }
- };
- this.addItem = function(item) {
- this.chain.push(new async.AsyncCall(
- item.func, item.args || [], item.callback, item.context));
- };
- // alias
- this.push = this.addItem;
- 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) {
- var 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;
|