async.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /*
  2. * Utilities: A classic collection of JavaScript utilities
  3. * Copyright 2112 Matthew Eernisse ([email protected])
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. var async = {};
  19. /*
  20. AsyncChain -- performs a list of asynchronous calls in a desired order.
  21. Optional "last" method can be set to run after all the items in the
  22. chain have completed.
  23. // Example usage
  24. var asyncChain = new async.AsyncChain([
  25. {
  26. func: app.trainToBangkok,
  27. args: [geddy, neil, alex],
  28. callback: null, // No callback for this action
  29. },
  30. {
  31. func: fs.readdir,
  32. args: [geddy.config.dirname + '/thailand/express'],
  33. callback: function (err, result) {
  34. if (err) {
  35. // Bail out completely
  36. arguments.callee.chain.abort();
  37. }
  38. else if (result.theBest) {
  39. // Don't run the next item in the chain; go directly
  40. // to the 'last' method.
  41. arguments.callee.chain.shortCircuit();
  42. }
  43. else {
  44. // Otherwise do some other stuff and
  45. // then go to the next link
  46. }
  47. }
  48. },
  49. {
  50. func: child_process.exec,
  51. args: ['ls ./'],
  52. callback: this.hitTheStops
  53. }
  54. ]);
  55. // Function to exec after all the links in the chain finish
  56. asyncChain.last = function () { // Do some final stuff };
  57. // Start the async-chain
  58. asyncChain.run();
  59. */
  60. async.execNonBlocking = function (func) {
  61. if (typeof process != 'undefined' && typeof process.nextTick == 'function') {
  62. process.nextTick(func);
  63. }
  64. else {
  65. setTimeout(func, 0);
  66. }
  67. };
  68. async.AsyncBase = new (function () {
  69. this.init = function (chain) {
  70. var item;
  71. this.chain = [];
  72. this.currentItem = null;
  73. this.shortCircuited = false;
  74. this.shortCircuitedArgs = undefined;
  75. this.aborted = false;
  76. for (var i = 0; i < chain.length; i++) {
  77. item = chain[i];
  78. this.addItem(item);
  79. }
  80. };
  81. this.addItem = function(item) {
  82. this.chain.push(new async.AsyncCall(
  83. item.func, item.args || [], item.callback, item.context));
  84. };
  85. // alias
  86. this.push = this.addItem;
  87. this.runItem = function (item) {
  88. // Reference to the current item in the chain -- used
  89. // to look up the callback to execute with execCallback
  90. this.currentItem = item;
  91. // Scopage
  92. var _this = this;
  93. // Pass the arguments passed to the current async call
  94. // to the callback executor, execute it in the correct scope
  95. var executor = function () {
  96. _this.execCallback.apply(_this, arguments);
  97. };
  98. // Append the callback executor to the end of the arguments
  99. // Node helpfully always has the callback func last
  100. var args = item.args.concat(executor);
  101. var func = item.func;
  102. // Run the async call
  103. func.apply(item.context, args);
  104. };
  105. this.next = function () {
  106. if (this.chain.length) {
  107. this.runItem(this.chain.shift());
  108. }
  109. else {
  110. this.last();
  111. }
  112. };
  113. this.execCallback = function () {
  114. // Look up the callback, if any, specified for this async call
  115. var callback = this.currentItem.callback;
  116. // If there's a callback, do it
  117. if (callback && typeof callback == 'function') {
  118. // Allow access to the chain from inside the callback by setting
  119. // callback.chain = this, and then using arguments.callee.chain
  120. callback.chain = this;
  121. callback.apply(this.currentItem.context, arguments);
  122. }
  123. this.currentItem.finished = true;
  124. // If one of the async callbacks called chain.shortCircuit,
  125. // skip to the 'last' function for the chain
  126. if (this.shortCircuited) {
  127. this.last.apply(null, this.shortCircuitedArgs);
  128. }
  129. // If one of the async callbacks called chain.abort,
  130. // bail completely out
  131. else if (this.aborted) {
  132. return;
  133. }
  134. // Otherwise run the next item, if any, in the chain
  135. // Let's try not to block if we don't have to
  136. else {
  137. // Scopage
  138. var _this = this;
  139. async.execNonBlocking(function () { _this.next.call(_this); });
  140. }
  141. }
  142. // Short-circuit the chain, jump straight to the 'last' function
  143. this.shortCircuit = function () {
  144. this.shortCircuitedArgs = arguments;
  145. this.shortCircuited = true;
  146. }
  147. // Stop execution of the chain, bail completely out
  148. this.abort = function () {
  149. this.aborted = true;
  150. }
  151. // Kick off the chain by grabbing the first item and running it
  152. this.run = this.next;
  153. // Function to run when the chain is done -- default is a no-op
  154. this.last = function () {};
  155. })();
  156. async.AsyncChain = function (chain) {
  157. this.init(chain);
  158. };
  159. async.AsyncChain.prototype = async.AsyncBase;
  160. async.AsyncGroup = function (group) {
  161. var item;
  162. var callback;
  163. var args;
  164. this.group = [];
  165. this.outstandingCount = 0;
  166. for (var i = 0; i < group.length; i++) {
  167. item = group[i];
  168. this.group.push(new async.AsyncCall(
  169. item.func, item.args, item.callback, item.context));
  170. this.outstandingCount++;
  171. }
  172. };
  173. /*
  174. Simpler way to group async calls -- doesn't ensure completion order,
  175. but still has a "last" method called when the entire group of calls
  176. have completed.
  177. */
  178. async.AsyncGroup.prototype = new function () {
  179. this.run = function () {
  180. var _this = this
  181. , group = this.group
  182. , item
  183. , createItem = function (item, args) {
  184. return function () {
  185. item.func.apply(item.context, args);
  186. };
  187. }
  188. , createCallback = function (item) {
  189. return function () {
  190. if (item.callback) {
  191. item.callback.apply(null, arguments);
  192. }
  193. _this.finish.call(_this);
  194. }
  195. };
  196. for (var i = 0; i < group.length; i++) {
  197. item = group[i];
  198. callback = createCallback(item);
  199. args = item.args.concat(callback);
  200. // Run the async call
  201. async.execNonBlocking(createItem(item, args));
  202. }
  203. };
  204. this.finish = function () {
  205. this.outstandingCount--;
  206. if (!this.outstandingCount) {
  207. this.last();
  208. };
  209. };
  210. this.last = function () {};
  211. };
  212. var _createSimpleAsyncCall = function (func, context) {
  213. return {
  214. func: func
  215. , args: []
  216. , callback: function () {}
  217. , context: context
  218. };
  219. };
  220. async.SimpleAsyncChain = function (funcs, context) {
  221. var chain = [];
  222. for (var i = 0, ii = funcs.length; i < ii; i++) {
  223. chain.push(_createSimpleAsyncCall(funcs[i], context));
  224. }
  225. this.init(chain);
  226. };
  227. async.SimpleAsyncChain.prototype = async.AsyncBase;
  228. async.AsyncCall = function (func, args, callback, context) {
  229. this.func = func;
  230. this.args = args;
  231. this.callback = callback || null;
  232. this.context = context || null;
  233. };
  234. async.Initializer = function (steps, callback) {
  235. var self = this;
  236. this.steps = {};
  237. this.callback = callback;
  238. // Create an object-literal of the steps to tick off
  239. steps.forEach(function (step) {
  240. self.steps[step] = false;
  241. });
  242. };
  243. async.Initializer.prototype = new (function () {
  244. this.complete = function (step) {
  245. var steps = this.steps;
  246. // Tick this step off
  247. steps[step] = true;
  248. // Iterate the steps -- if any are not done, bail out
  249. for (var p in steps) {
  250. if (!steps[p]) {
  251. return false;
  252. }
  253. }
  254. // If all steps are done, run the callback
  255. this.callback();
  256. };
  257. })();
  258. module.exports = async;