index.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // To do: remove `void`s
  2. // To do: remove `null` from output of our APIs, allow it as user APIs.
  3. /**
  4. * @typedef {(error?: Error | null | undefined, ...output: Array<any>) => void} Callback
  5. * Callback.
  6. *
  7. * @typedef {(...input: Array<any>) => any} Middleware
  8. * Ware.
  9. *
  10. * @typedef Pipeline
  11. * Pipeline.
  12. * @property {Run} run
  13. * Run the pipeline.
  14. * @property {Use} use
  15. * Add middleware.
  16. *
  17. * @typedef {(...input: Array<any>) => void} Run
  18. * Call all middleware.
  19. *
  20. * Calls `done` on completion with either an error or the output of the
  21. * last middleware.
  22. *
  23. * > 👉 **Note**: as the length of input defines whether async functions get a
  24. * > `next` function,
  25. * > it’s recommended to keep `input` at one value normally.
  26. *
  27. * @typedef {(fn: Middleware) => Pipeline} Use
  28. * Add middleware.
  29. */
  30. /**
  31. * Create new middleware.
  32. *
  33. * @returns {Pipeline}
  34. * Pipeline.
  35. */
  36. export function trough() {
  37. /** @type {Array<Middleware>} */
  38. const fns = []
  39. /** @type {Pipeline} */
  40. const pipeline = {run, use}
  41. return pipeline
  42. /** @type {Run} */
  43. function run(...values) {
  44. let middlewareIndex = -1
  45. /** @type {Callback} */
  46. const callback = values.pop()
  47. if (typeof callback !== 'function') {
  48. throw new TypeError('Expected function as last argument, not ' + callback)
  49. }
  50. next(null, ...values)
  51. /**
  52. * Run the next `fn`, or we’re done.
  53. *
  54. * @param {Error | null | undefined} error
  55. * @param {Array<any>} output
  56. */
  57. function next(error, ...output) {
  58. const fn = fns[++middlewareIndex]
  59. let index = -1
  60. if (error) {
  61. callback(error)
  62. return
  63. }
  64. // Copy non-nullish input into values.
  65. while (++index < values.length) {
  66. if (output[index] === null || output[index] === undefined) {
  67. output[index] = values[index]
  68. }
  69. }
  70. // Save the newly created `output` for the next call.
  71. values = output
  72. // Next or done.
  73. if (fn) {
  74. wrap(fn, next)(...output)
  75. } else {
  76. callback(null, ...output)
  77. }
  78. }
  79. }
  80. /** @type {Use} */
  81. function use(middelware) {
  82. if (typeof middelware !== 'function') {
  83. throw new TypeError(
  84. 'Expected `middelware` to be a function, not ' + middelware
  85. )
  86. }
  87. fns.push(middelware)
  88. return pipeline
  89. }
  90. }
  91. /**
  92. * Wrap `middleware` into a uniform interface.
  93. *
  94. * You can pass all input to the resulting function.
  95. * `callback` is then called with the output of `middleware`.
  96. *
  97. * If `middleware` accepts more arguments than the later given in input,
  98. * an extra `done` function is passed to it after that input,
  99. * which must be called by `middleware`.
  100. *
  101. * The first value in `input` is the main input value.
  102. * All other input values are the rest input values.
  103. * The values given to `callback` are the input values,
  104. * merged with every non-nullish output value.
  105. *
  106. * * if `middleware` throws an error,
  107. * returns a promise that is rejected,
  108. * or calls the given `done` function with an error,
  109. * `callback` is called with that error
  110. * * if `middleware` returns a value or returns a promise that is resolved,
  111. * that value is the main output value
  112. * * if `middleware` calls `done`,
  113. * all non-nullish values except for the first one (the error) overwrite the
  114. * output values
  115. *
  116. * @param {Middleware} middleware
  117. * Function to wrap.
  118. * @param {Callback} callback
  119. * Callback called with the output of `middleware`.
  120. * @returns {Run}
  121. * Wrapped middleware.
  122. */
  123. export function wrap(middleware, callback) {
  124. /** @type {boolean} */
  125. let called
  126. return wrapped
  127. /**
  128. * Call `middleware`.
  129. * @this {any}
  130. * @param {Array<any>} parameters
  131. * @returns {void}
  132. */
  133. function wrapped(...parameters) {
  134. const fnExpectsCallback = middleware.length > parameters.length
  135. /** @type {any} */
  136. let result
  137. if (fnExpectsCallback) {
  138. parameters.push(done)
  139. }
  140. try {
  141. result = middleware.apply(this, parameters)
  142. } catch (error) {
  143. const exception = /** @type {Error} */ (error)
  144. // Well, this is quite the pickle.
  145. // `middleware` received a callback and called it synchronously, but that
  146. // threw an error.
  147. // The only thing left to do is to throw the thing instead.
  148. if (fnExpectsCallback && called) {
  149. throw exception
  150. }
  151. return done(exception)
  152. }
  153. if (!fnExpectsCallback) {
  154. if (result && result.then && typeof result.then === 'function') {
  155. result.then(then, done)
  156. } else if (result instanceof Error) {
  157. done(result)
  158. } else {
  159. then(result)
  160. }
  161. }
  162. }
  163. /**
  164. * Call `callback`, only once.
  165. *
  166. * @type {Callback}
  167. */
  168. function done(error, ...output) {
  169. if (!called) {
  170. called = true
  171. callback(error, ...output)
  172. }
  173. }
  174. /**
  175. * Call `done` with one value.
  176. *
  177. * @param {any} [value]
  178. */
  179. function then(value) {
  180. done(null, value)
  181. }
  182. }