forked from a-synchronous/rubico
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pipe.js
108 lines (104 loc) · 3.04 KB
/
pipe.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
const funcConcat = require('./_internal/funcConcat')
const funcConcatSync = require('./_internal/funcConcatSync')
const isGeneratorFunction = require('./_internal/isGeneratorFunction')
const isAsyncGeneratorFunction = require('./_internal/isAsyncGeneratorFunction')
/**
* @name pipe
*
* @synopsis
* ```coffeescript [specscript]
* Reducer<T> = (any, T)=>Promise|any
*
* var args ...any,
* funcs [
* ...args=>Promise|any,
* ...Array<any=>Promise|any>,
* ],
* transducers Array<Reducer=>Reducer>
* reducer Reducer,
*
* pipe(funcs)(...args) -> Promise|any
*
* pipe(transducers)(reducer) -> Reducer
* ```
*
* @description
* Create a function pipeline, where each function passes its return value as a single argument to the next function until all functions have executed. The result of a pipeline execution is the return of its last function.
*
* ```javascript [playground]
* console.log(
* pipe([
* number => number + 1,
* number => number + 2,
* number => number + 3,
* ])(5),
* ) // 11
* ```
*
* When passed a reducer in argument position, a function pipeline composes the reducer such that the transducers are applied in series, calling the reducer as the last step to end the chain. The resulting reducer has chained transducing functionality; note however that it must be used in conjunction with `transform` or `reduce` to have a transducing effect. For more information on this behavior, see [this blog post on transducers](https://rubico.land/blog/2020/10/02/transducers-crash-course).
*
* ```javascript [playground]
* const isOdd = number => number % 2 == 1
*
* const square = number => number ** 2
*
* const add = (a, b) => a + b
*
* const squaredOdds = pipe([
* filter(isOdd),
* map(square),
* ])
*
* console.log(
* [1, 2, 3, 4, 5].reduce(squaredOdds(add), 0),
* ) // 35
*
* console.log(
* squaredOdds([1, 2, 3, 4, 5])
* ) // [1, 9, 25]
* ```
*
* @execution series
*
* @transducing
*/
const pipe = function (funcs) {
const functionPipeline = funcs.reduce(funcConcat),
functionComposition = funcs.reduceRight(funcConcat)
return function pipeline(...args) {
const firstArg = args[0]
if (
typeof firstArg == 'function'
&& !isGeneratorFunction(firstArg)
&& !isAsyncGeneratorFunction(firstArg)
) {
return functionComposition(...args)
}
return functionPipeline(...args)
}
}
// funcs Array<function> -> pipeline function
const pipeSync = funcs => funcs.reduce(funcConcatSync)
/**
* @name pipe.sync
*
* @synopsis
* ```coffeescript [specscript]
* var args ...any,
* funcs [...args=>any, ...Array<any=>any>]
*
* pipe.sync(funcs) -> syncPipeline ...args=>any
* ```
*
* @description
* `pipe` that doesn't automatically resolve promises. This variant is a good option if more performance is desired or if manual promise handling is required.
*
* ```javascript [playground]
* pipe.sync([
* value => Promise.resolve(value),
* promise => promise.then(console.log)
* ])('hey') // hey
* ```
*/
pipe.sync = pipeSync
module.exports = pipe