hooks.alt.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /**
  2. * Hooks are useful if we want to add a method that automatically has `pre` and `post` hooks.
  3. * For example, it would be convenient to have `pre` and `post` hooks for `save`.
  4. * _.extend(Model, mixins.hooks);
  5. * Model.hook('save', function () {
  6. * console.log('saving');
  7. * });
  8. * Model.pre('save', function (next, done) {
  9. * console.log('about to save');
  10. * next();
  11. * });
  12. * Model.post('save', function (next, done) {
  13. * console.log('saved');
  14. * next();
  15. * });
  16. *
  17. * var m = new Model();
  18. * m.save();
  19. * // about to save
  20. * // saving
  21. * // saved
  22. */
  23. // TODO Add in pre and post skipping options
  24. module.exports = {
  25. /**
  26. * Declares a new hook to which you can add pres and posts
  27. * @param {String} name of the function
  28. * @param {Function} the method
  29. * @param {Function} the error handler callback
  30. */
  31. hook: function (name, fn, err) {
  32. if (arguments.length === 1 && typeof name === 'object') {
  33. for (var k in name) { // `name` is a hash of hookName->hookFn
  34. this.hook(k, name[k]);
  35. }
  36. return;
  37. }
  38. if (!err) err = fn;
  39. var proto = this.prototype || this
  40. , pres = proto._pres = proto._pres || {}
  41. , posts = proto._posts = proto._posts || {};
  42. pres[name] = pres[name] || [];
  43. posts[name] = posts[name] || [];
  44. function noop () {}
  45. proto[name] = function () {
  46. var self = this
  47. , pres = this._pres[name]
  48. , posts = this._posts[name]
  49. , numAsyncPres = 0
  50. , hookArgs = [].slice.call(arguments)
  51. , preChain = pres.map( function (pre, i) {
  52. var wrapper = function () {
  53. if (arguments[0] instanceof Error)
  54. return err(arguments[0]);
  55. if (numAsyncPres) {
  56. // arguments[1] === asyncComplete
  57. if (arguments.length)
  58. hookArgs = [].slice.call(arguments, 2);
  59. pre.apply(self,
  60. [ preChain[i+1] || allPresInvoked,
  61. asyncComplete
  62. ].concat(hookArgs)
  63. );
  64. } else {
  65. if (arguments.length)
  66. hookArgs = [].slice.call(arguments);
  67. pre.apply(self,
  68. [ preChain[i+1] || allPresDone ].concat(hookArgs));
  69. }
  70. }; // end wrapper = function () {...
  71. if (wrapper.isAsync = pre.isAsync)
  72. numAsyncPres++;
  73. return wrapper;
  74. }); // end posts.map(...)
  75. function allPresInvoked () {
  76. if (arguments[0] instanceof Error)
  77. err(arguments[0]);
  78. }
  79. function allPresDone () {
  80. if (arguments[0] instanceof Error)
  81. return err(arguments[0]);
  82. if (arguments.length)
  83. hookArgs = [].slice.call(arguments);
  84. fn.apply(self, hookArgs);
  85. var postChain = posts.map( function (post, i) {
  86. var wrapper = function () {
  87. if (arguments[0] instanceof Error)
  88. return err(arguments[0]);
  89. if (arguments.length)
  90. hookArgs = [].slice.call(arguments);
  91. post.apply(self,
  92. [ postChain[i+1] || noop].concat(hookArgs));
  93. }; // end wrapper = function () {...
  94. return wrapper;
  95. }); // end posts.map(...)
  96. if (postChain.length) postChain[0]();
  97. }
  98. if (numAsyncPres) {
  99. complete = numAsyncPres;
  100. function asyncComplete () {
  101. if (arguments[0] instanceof Error)
  102. return err(arguments[0]);
  103. --complete || allPresDone.call(this);
  104. }
  105. }
  106. (preChain[0] || allPresDone)();
  107. };
  108. return this;
  109. },
  110. pre: function (name, fn, isAsync) {
  111. var proto = this.prototype
  112. , pres = proto._pres = proto._pres || {};
  113. if (fn.isAsync = isAsync) {
  114. this.prototype[name].numAsyncPres++;
  115. }
  116. (pres[name] = pres[name] || []).push(fn);
  117. return this;
  118. },
  119. post: function (name, fn, isAsync) {
  120. var proto = this.prototype
  121. , posts = proto._posts = proto._posts || {};
  122. (posts[name] = posts[name] || []).push(fn);
  123. return this;
  124. }
  125. };