index.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. var Promise = require('any-promise');
  2. var co = require('co');
  3. module.exports = compose;
  4. compose.Wrap = Wrap;
  5. function compose(middleware) {
  6. return function (next) {
  7. next = next || new Wrap(noop);
  8. var i = middleware.length;
  9. while (i--) next = new Wrap(middleware[i], this, next);
  10. return next
  11. }
  12. }
  13. /**
  14. * Wrap a function, then lazily call it,
  15. * always returning both a promise and a generator.
  16. *
  17. * @param {Function} fn
  18. * @param {Object} ctx
  19. * @param {Wrap} next
  20. */
  21. function Wrap(fn, ctx, next) {
  22. if (typeof fn !== 'function') throw TypeError('Not a function!');
  23. this._fn = fn;
  24. this._ctx = ctx;
  25. this._next = next;
  26. this._called = false;
  27. this._value = undefined;
  28. this._promise = undefined;
  29. this._generator = undefined;
  30. }
  31. /**
  32. * Lazily call the function.
  33. * Note that if it's not an async or generator function,
  34. *
  35. * @returns {Mixed}
  36. */
  37. Wrap.prototype._getValue = function () {
  38. if (!this._called) {
  39. this._called = true;
  40. try {
  41. this._value = this._fn.call(this._ctx, this._next);
  42. } catch (e) {
  43. this._value = Promise.reject(e);
  44. }
  45. }
  46. return this._value
  47. };
  48. /**
  49. * Lazily create a promise from the return value.
  50. *
  51. * @returns {Promise}
  52. */
  53. Wrap.prototype._getPromise = function () {
  54. if (this._promise === undefined) {
  55. var value = this._getValue();
  56. this._promise = isGenerator(value)
  57. ? co.call(this._ctx, value)
  58. : Promise.resolve(value);
  59. }
  60. return this._promise
  61. }
  62. /**
  63. * Lazily create a generator from the return value.
  64. *
  65. * @returns {Iterator}
  66. */
  67. Wrap.prototype._getGenerator = function () {
  68. if (this._generator === undefined) {
  69. var value = this._getValue();
  70. this._generator = isGenerator(value)
  71. ? value
  72. : promiseToGenerator.call(this._ctx, value);
  73. }
  74. return this._generator
  75. }
  76. /**
  77. * In later version of v8,
  78. * `yield*` works on the `[@@iterator]` method.
  79. *
  80. * @returns {Iterator}
  81. */
  82. if (typeof Symbol !== 'undefined') {
  83. Wrap.prototype[Symbol.iterator] = function () {
  84. return this._getGenerator();
  85. }
  86. }
  87. /**
  88. * This creates a generator from a promise or a value.
  89. *
  90. * TODO: try to avoid using a generator function for this.
  91. *
  92. * @returns {Iterator}
  93. */
  94. var loggedDeprecationMessage = false;
  95. function* promiseToGenerator(promise) {
  96. if (!loggedDeprecationMessage) {
  97. console.error('A promise was converted into a generator, which is an anti-pattern. Please avoid using `yield* next`!')
  98. loggedDeprecationMessage = true;
  99. }
  100. return yield Promise.resolve(promise);
  101. }
  102. /**
  103. * Proxy generator and promise methods.
  104. */
  105. Wrap.prototype.then = function (resolve, reject) {
  106. return this._getPromise().then(resolve, reject);
  107. }
  108. Wrap.prototype.catch = function (reject) {
  109. return this._getPromise().catch(reject);
  110. }
  111. Wrap.prototype.next = function (val) {
  112. return this._getGenerator().next(val);
  113. }
  114. Wrap.prototype.throw = function (err) {
  115. return this._getGenerator().throw(err);
  116. }
  117. /**
  118. * Check if `obj` is a generator.
  119. *
  120. * @param {Mixed} obj
  121. * @return {Boolean}
  122. */
  123. function isGenerator(obj) {
  124. return obj
  125. && typeof obj.next === 'function'
  126. && typeof obj.throw === 'function';
  127. }
  128. function noop() {}