Currently, the Pipeline operator is one of the more interesting proposals advancing through the TC39.
The operator |>
is borrowed and inspired by several functional languages such as F#, Elm, and Elixir, and UNIX pipes as well. (The original syntax was ::
, which proved too controversial) According to the TC39 proposal
It’s a backwards-compatible way of streamlining chained function calls in a readable, functional manner, and provides a practical alternative to extending built-in prototypes.
which makes it very useful for short single-argument functions.
In the simplest example, the pipeline operator turns alert('Hello!')
into 'Hello!' |> alert
. This is not the best uage of the operator, as it doesn’t aid the readability in any way. The better usage comes when you chain several functions together.
Take for example these functions:
function add5 (number) {
return number + 5;
}
function subtract3 (number) {
return number - 3;
}
function multiplyBy10 (number) {
return number * 10;
}
All pretty straightforward. Now we can do this:
// ES6
const number = multiplyBy10(add5(subtract3(5)))
console.log(number) // 70
// Future
const number = 5
|> subtract3
|> add5
|> multiplyBy10
This is slightly opinionated, but I think the pipeline operator aids in readability here. Note that the order goes by the function called first, which is especially useful since I know the f(g) versus g(f) can get confusing for particularly long-chained functions.
Let’s do an example involving multiple arguments.
function makeLowerCase(str) { return str.toLowerCase() }
function concat(strA, strB) { return strA + strB }
function writeSentence (subject, verb, object) {
return `${subject} ${verb} a ${object}`
}
const apple = 'APPLE'
const sentence = apple
|> makeLowerCase
|> (s => concat('pine', s))
|> (_ => writeSentence('I', 'ate', _))
console.log(sentence) // "I ate a pineapple"
While it seems a bit odd at first, we’re just simple piping in arrow functions that handle the previous result from the preceeding pipe. When we took ‘APPLE’, we lowercased it, and then concatenated ‘pine’ with ‘apple’. Hence, pineapple became the new argument for the last function, which wrote our sentence with pineapple as its subject.
You could also write the function as writeSentence('I', 'ate', concat('pine', makeLowerCase(apple)))
though deciding between compactness or readability is up to you.
Depending on the papp proposal, it may be able to write this:
const sentence = apple
|> makeLowerCase
|> concat.papp('pine')
|> writeSentence.papp('I', 'ate')
papp
is used as a way to partially apply the function’s parameters, leaving the rest to be used by the pipelines.
Async/Await usage also works nicely here. To borrow directly from the proposal’s page:
x |> await f
// is equivalent to
await f(x)
You can await the result of an async function and pass it to the next function, like so:
const userAge = userId |> await fetchUsersById |> getAgeFromUser
// is equivalent to
const userAge = getAgeFromUser(await fetchUserById(userId))
A more complex example
async function runOperationsOnFile () {
fs.readFile('./post.txt')
|> await
|> file => file
.split('\n')
.map(fs.readFile)
|> Promise.all
|> await
|> all => all.join('\n')
|> console.log
}
// post.txt
fileA.txt
fileB.txt
//fileA
Hello
//fileB
World!
// Result of runOperationsOnFile
Hello
World!
While the pipeline operator is currently a stage 1 (very experimental) feature, you can find other resources here: