Interlude: Rethinking the JavaScript Pipeline Operator

Updated: 4 days ago


Photo by Martin Adams on Unsplash


My latest two blog posts were about implementing a small, lightweight iteration library in JavaScript. The first post described how such a library can be implemented for every iterable collection, and the second post took it to the next level by introducing support for iterating over asynchronous sequences. Both posts received very positive feedback with one exception: my use of the proposed pipeline operator.


For those not familiar with the pipeline operator proposal, it’s currently a “stage 1” proposal to TC39 (the committee in charge of the ECMAScript stardandard). This means it’s being actively promoted, but isn’t yet at a stage where it’s expected to be eventually included in the standard. In other words, it’s definitely not there yet.


Why did I decide to use it in my posts if it’s not yet part of the ECMAScript stardandard, and may never be? The reasons for this were ease of use and readability. The library I presented consists of multiple simple iteration functions, which are composed to implement sophisticated data flows and algorithms. Due to the way in which functions are composed in JavaScript, this can make the code difficult to write and read. For example, filtering, mapping and extracting values from a collection using this library is implemented as:



As you can see, the functions must be written in the reverse sequence from the order in which they are used. In addition, it’s difficult to tell which arrow function is associated with which iteration function.


The proposed pipeline operator was specifically designed to overcome these issues:



This code is much easier to follow and reason about because the functions appear in the order in which they are used. In addition, all their arguments are placed together (through the use of # as an argument placeholder). No need to maintain a mental stack of functions in your head, or try to match parentheses across multiple function expressions.



Community Pushback



Why, then, was there such a pushback against my use of this proposed feature? One reason is that some people dislike the use of language features that are not yet part of the ECMAScript standard, or at least at stage 3. I can appreciate that - there have been cases where developers have jumped the gun and used such features only to have them rolled back and modified by TC39. This has happened with the decorators proposal, for example. The situation with the pipeline operator proposal is especially delicate in this context because there are, in fact, two competing proposals for the implementation of this language feature.


However, the main reason for the pushback, based on the replies I received and some discussions I conducted, is that many JavaScript devs simply dislike the pipeline operator proposal altogether. Significantly so. For example, Costin Manda was inspired to write a blog and a library in response to my original post, in which he wrote:


The post suggested creating functions that would use the modern Javascript features of iterators and generator functions and supplant the standard Array functions. It was brilliant!


And then the post abruptly veered and swerved into a tree. The author suggested we add the pipe operator to Javascript, just like they have in functional programming languages, so that we can use those static functions with hash characters as placeholders and... ugh! It was horrid!


I can definitely appreciate the sentiment. I too am not a fan of extensions to the language that appear to deviate from its existing syntax and semantics. That said, the alternative to the pipeline operator which Costin used - method chaining with the dot operator - does, in fact, highlight the benefits of piping over chaining. Indeed, this is something I alluded to in the very title of my original post: breaking chains with pipelines.



Kyle Simpson to the rescue!


It was my good friend and well-known JavaScript guru Kyle Simpson (@getify) who proposed a good alternative to the pipeline operator, which doesn’t require extending the language yet provides all its benefits. So good, in fact, that it fit inside a single tweet:



The pipe() function proposed by Kyle is a higher-order function which accepts any number of function references as arguments, and returns a function that invokes these functions in sequence on whatever argument it receives.


After a bit of back-and-forth we settled on the following simple implementation of the pipe() function for this use-case:



Rather than accepting the function arguments and returning another function which takes the input argument, this implementation takes both together, and places the input argument first. This enables putting the input argument at the front rather than at the end of the expression, as in Kyle’s original code snippet, and eliminates the extra parenthesis. This results in code that is very similar to the implementation which uses the pipeline operator:



This implementation of pipe() does require modification of the filter(), map(), and slice() functions that I presented in my original article. For example, the original filter() function was implemented as:


The new implementation is:



This modification is required because the # argument placeholder, introduced as part of one pipeline implementation proposal, is no longer available. Instead the functions receive just the operator and return a function which is then invoked on the sequence. In my opinion, this is a very small price to pay for eliminating the need to extend the language. I leave it to you to figure out the required modifications to other functions I presented.



Adding asynchronicity


In my second blog post I showed how the iteration library can be extended to also support asynchronous sequences, such as a sequence of events generated by a browser DOM element. And the proposed pipeline operator was able to work with those types of sequences as well, for example:



Similarly, the pipe() function presented above can also work with asynchronous sequences, but the iteration functions need to be slightly modified. For example, the original forEach() function was:



Now it needs to be:




Now, we can rewrite the example that pipes DOM events as:



As usual I will leave it up to you to modify the implementation of reduce() as well as the other iteration functions. Or you can check out this CodePen in which I provide the relevant implementations and examples of usage.



Summary


It’s generally preferable to avoid extending a programming language when existing language specification is already sufficiently expressive in the relevant context. This is especially true when the proposed extension deviates from the existing syntax and semantics of the programming language. As this post shows, I now definitely believe that this is the case with the proposed pipeline operator. Consequently, I hope that TC39 decides to reject this proposal.


In any event, removing the use of the pipeline operator makes the resulting code compatible with modern browsers. If you check the CodePen associated with this post, you will see that I disabled the use of Babel. This makes the library much more usable and useful.



This post was written by Dan Shappir

You can follow him on Twitter


For more engineering updates and insights:

  • Black Twitter Icon
  • Black YouTube Icon

At Wix Engineering we develop some of the most innovative cloud-based web applications that influence our +180 million users worldwide.

Have any questions?
Email: wixeng@wix.com

Trademarks and logos of other parties appearing in this post are the property of their respective holders.

Get Wix Engineering Straight to Your e-mail