3 features that could change the future of JavaScript - Heart Internet Blog - Focusing on all aspects of the web

Do you want to discover the next exciting JavaScript features that you didn’t even know you needed? Let me show you three proposals that may change the way you write code the same way the spread operator did.

However, here’s a small disclaimer:

All of these features are under development and discussion. The goal here is to add some hype around those features and create awareness of the hard work that TC39 is doing to find consensus, fix all the syntax and semantics and have them shipped with the next releases of ECMAScript. If you have any concerns, comments or desire to express your support, please go to the TC39 proposals repository, add a star to the feature you would like to support, open an issue to voice your concerns and get involved.

But before I present the first proposal, I want to ask a simple (but tricky) question:

What is ‘this’?

In ECMAScript, this has a different semantic than this in many other programming languages, where this often refers to the lexical scope. Let’s break this behaviour down into small examples:

‘This’ in the global scope

What is the value of this in this example?

console.info(this);

At the global scope, this refers to the global object, like the window in the browser, self on web workers and the module.exports object in NodeJS.

‘This’ in the function scope

At the function scope, this behaves depending on how the function is called, and this aspect makes it tricky to predict its value. We can understand it better by checking the following example:

<button id="button">🐱 🐾</button>
<script>
  class MeowctComponent {
    constructor() {
      this.paw = document.getElementById('button');
    }

    meow() {
      console.info('🐱 on this: ', this.paw);
    }
  }

  const cat = new MeowctComponent();
  cat.paw.addEventListener('click', cat.meow);
</script>

In the example above, I created a MeowctComponent, which has only one property paw that points to the button element and one method called meow that should print the paw instance property into the console.

The tricky part is that the meow method is executed only when the button is clicked, and because of that, this carries a button tag as context, and since the button tag does not have any paw property, it logs the undefined value into the console. Tricky, isn’t it?

To fix this specific behaviour we can leverage the Function.prototype.bind() method to explicitly bind this to the cat instance, like in the following example:

<button id="button">Meow</button>
<script>
class MeowctComponent {
constructor() {
this.paw = document.getElementById('button');
}

meow() {
console.info('🐱 on this: ', this.paw);
}
}

const cat = new MeowctComponent();
cat.paw.addEventListener('click', cat.meow.bind(cat));
</script>

The method .bind() returns a permanently bound function to the first given parameter, which is the context. Now, because we bound the cat.meow method to the cat instance, this.paw inside the meow method correctly points to the button element.

As an alternative to the Function.prototype.bind() method, we can use the arrow function to achieve the same result. It keeps the value of the lexical this of the surrounding context and dispenses the need to bind the context explicitly, like in the next example:

<button id="button">🐱 Meow</button>
<script>
class MeowctComponent {
constructor() {
this.paw = document.getElementById('button');
}

meow() {
console.info('🐱 on this: ', this.paw);
}
}

const cat = new MeowctComponent();
cat.paw.addEventListener('click', () => cat.meow());
</script>

Although arrow functions solve the majority of use cases where we need to bind the lexical this explicitly, we still have two use cases for which the use of the explicit bind is needed.

Calling a known function using this to provide context:

let hasOwnProp = Object.prototype.hasOwnProperty;
let obj = Object.create(null);

obj.hasOwnProperty('x') // Type Error...

hasOwnProp.call(obj, "x"); //false

obj.x = 100;

hasOwnProp.call(obj, "x"); // true

Let’s suppose for any reason we have this obj object that doesn’t extend Object.prototype but we need to check if obj has an x property by using the hasOwnProperty method from Object.prototype. To achieve that, we have to use the call method and explicitly pass obj as the first parameter to make it work as expected, which appears not to be so idiomatic.

Extracting a method

The second case can be spotted when we need to extract a method from an object like in our MeowctComponent example:

<button id="button">🐱 🐾</button>
<script>
class MeowctComponent {
constructor() {
this.paw = document.getElementById('button');
}

meow() {
console.info('🐱 on this: ', this.paw);
}
}

const cat = new MeowctComponent();
cat.paw.addEventListener('click', cat.meow.bind(cat));
</script>

These use cases are the perfect hook for presenting the first TC39 proposal.

The Bind Operator ::

The Bind operator consists of an introduction of a new operator :: (double colon), which acts as syntax sugar for the previous two use cases. It comes in two formats: binary and unary.

In its binary form, the bind operator creates a function with its left side bound to this of the right side, like in the following example:

let hasOwnProp = Object.prototype.hasOwnProperty;
let obj = Object.create(null);

obj.hasOwnProperty('x') // Type Error...

obj::hasOwnProp("x"); //false

obj.x = 100;

obj::hasOwnProp("x"); // true

That looks more natural, doesn’t it?

In its unary form, the operator creates a function bound to the base of the provided reference as a value for this variable, like in the following example:

...
cat.paw.addEventListener('click', ::cat.meow);
// which desugars to
cat.paw.addEventListener('click', cat.meow.bind(cat));
...

What’s so cool about the bind operator is the fact that it opens up new opportunities for creating virtual methods, as in this example of lib for iterable.

import { map, takeWhile, forEach } from "iterlib";

getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));

It’s super useful because the developer doesn’t need to download the whole lib to do small stuff, which reduces the impact on the bundle size of production applications. Besides, it makes those kinds of libs easier to extend.

The author of the bind operator is Kevin Smith, and this proposal is in Stage 0.

The Pipeline Operator

We just explored the benefits of having a virtual method for composition. However, with the bind operator, we have to rely on the this variable to be bound to a particular function to be able to compose behaviour. Some use cases require us to not rely on binding this to compose functionalities. Before introducing the next proposal, it’s a good idea to dig a little more into this use case.

So let’s imagine we have a request to create a function that sanitises a text entry from the user, then transforms all number symbols into their textual representation. How do we usually do this?

const userInput = document.getElementById('user-input').value;
const sanitizedInput = sanitize(userInput);
const textualizedNumberInput = textualizeNumbers(sanitizedInput);
console.log(textualizedNumberInput);

One possible solution could be achieved by the code above. It’s pretty standard, but to make it easy to read, we end up creating a lot of intermediary variables. You might want to improve it by just removing those variables. Let’s see what this would look like.

console.log(
textualizeNumbers(
sanitize(
document.getElementById('user-input').value
)
)
);

It seems that the snippet above can lead to messy code. To understand the data flow, the developer needs to look from the middle back up to the top, which doesn’t feel natural. Is there a better way to compose these functions without ending up with a deeply nested composition or the verbosity of intermediary variables? Enter the next proposal, the Pipeline Operator!

The pipeline operator minimal proposal

The Pipeline operator is syntax sugar for the use cases we presented above. It creates a way to streamline a chain of functions in a readable and functional manner. It’s backward compatible and provides an alternative for extending built-in prototypes. To illustrate how it can simplify the codebase, let’s see how the previous example could look with the pipeline operator.

document.getElementById('user-input').value
|> sanitize
|> textualizeNumbers
|> console.log

Let’s break this code down into pieces and show how intuitive it could be.

The first pipeline step could be called subject. The pipeline operator takes the subject and inserts it as a parameter to the next step, and the result of it becomes the subject of the next pipeline step, like in the following example:

document.getElementById('user-input').value

// ^ this is the subject of the next step...
|> sanitize

/* ^ ... that will be added as a parameter of this step which desugars to
sanitize(document.getElementById('user-input').value) which will be the

subject of the next step ...

*/
|> textualizeNumbers

/* ^ ... that will be added as a parameter of this step which desugars to
textualizeNumbers(sanitize(document.getElementById('user-input').value))

which will be the subject of the next step ...

*/
|> console.log

/* ^ ... that will be added as a parameter of this step which

finally desugars to

console.log(
textualizeNumbers(

sanitize(

document.getElementById('user-input').value

)

)

);

*/

Pipeline with more than one parameter

Let’s imagine that the textualizeNumbers function has a second parameter, which is a whitelist of numbers that shouldn’t be textualised, and we need to textualise only 0s and 1s. How should we add this to the pipeline chain?

document.getElementById('user-input').value
|> sanitize
|> (text => textualizeNumbers(text, ['0', '1']))
|> console.log

You could wrap your function that receives more than one parameter with an arrow function. Note that in the minimal proposal, arrow functions have to be wrapped with parenthesis; otherwise, you receive a syntax error. This parenthesis seems to be a syntax overhead, and we will cover how to work around this later.

Pipeline an async function

It’s time to make our base example a little more complicated. Let’s assume that our sanitize function treats the input on the server asynchronously and returns a promise. How do we deal with that?

document.getElementById('user-input').value
|> await sanitize

// ^ this is ambiguous

// await sanitize(x) or (await sanitize())(x)?
|> (text => textualizeNumbers(text, ['0', '1']))
|> console.log

We could use an await keyword to wait for the result and pass it forward, but there’s a problem with this code. It’s ambiguous, which makes it unclear how this code could be desugared and whether to use await sanitize(x) or (await sanitize())(x).

There are still some issues with the syntax and the semantics in the minimal proposal. For now there are two competing proposals, which try to address this: the Smart Pipeline Proposal and the F# Pipeline Proposal.

The Smart Pipeline Proposal

In the smart pipeline proposal, the previous example could be written like this:

document.getElementById('user-input').value
|> await sanitize(#)

// ^ whenever () or [] is needed we use Topic style
|> textualizeNumbers(#, ['0', '1']))

/*                     ^ This is the placeholder for the

*                     subject of the previous pipeline step

*/
|> console.log

// ^ Bare style

The smart pipeline proposal defines two styles and a placeholder token for a specific pipeline step: The Bare style and Topic style and #. Whenever the usage of parenthesis or square brackets is needed, the Topic style is required. Otherwise, the Bare style is used. The # placeholder means that the subject of the previous step should be placed in its place. The # placeholder is not final in the proposal, and this token could still be changed.

If you have a curried function, you have to use the Topic style; otherwise, you will get a syntax error, like in the example below:

x |> myCurried(10) // Syntax Error
x |> myCurried(10)(#) // Fine

This proposal has extensive documentation for further enhancements; you should check it out.

The F# syntax proposal

The F# pipeline proposal tries to address the same issues with less syntax overhead. It creates an await step, which helps to address the ambiguity we spotted in the minimal proposal.

document.getElementById('user-input').value
|> sanitize
|> await

// ^ await step
|> (text => textualizeNumbers(text, ['0', '1']))
|> console.log

The await step means that the previous step should be awaited to pass the subject to the next step. It desugars to the following code:

console.log(
textualizeNumbers(
await (
sanitize(document.getElementById('user-input').value
)
)
);

Note that it doesn’t address the “syntax overhead” of wrapping an arrow function with parenthesis, but we will discuss a workaround later.

This proposal is in stage 1, and the champion is Daniel Ehrenberg.

The Partial Application

When we were discussing the Pipeline operator, we saw an example of a pipeline step with some bound values and a wildcard, for which we use the computed value from the previous pipeline step.

...
textualizeNumbers(#, ['0', '1']))

//                ^ wildcard
...

This code could be considered a partial application, but what exactly does that mean?

The partial application refers to a process of giving a function with an N arity, returning another function with smaller arity by binding some of its parameters to a fixed value. Arity is the number of parameters that a specific function takes.

To present the concepts of the partial application, we can use the following code snippet.

const sayHi = (greetings, name, location) =>
console.log(`${greetings}, ${name}, from ${location}!`);

sayHi('Yo', 'James', 'Colombia');
// 'Yo, James from Colombia!

This snippet features a simple function, which receives three parameters to console.log a greetings phrase. So, let’s say we have only the two first values, and the third one needs to be taken inside a resolved promise. How can we do this?

const greetings = 'Hello';
const name = 'Maria';

// fetches the location from the server ¯\_(ツ)_/¯
getLocation()
.then(
location => sayHi(greetings, name, location)
);

This approach could be considered suboptimal, since we are creating two variables just for holding the value of the first two parameters. Maybe there’s a better way.

ECMAScript has a way to do the partial application by using Function.prototype.bind. Often, we forget that the .bind() method can bind not only the context but parameters as well. With that new trick in mind, let’s improve the previous example.

const sayHiByLocation = sayHi.bind(null, 'Hello', 'Maria');

getLocation()
.then(
location => sayHiByLocation(location)
);

Since we don’t need to bind a context, we are passing null as the first parameter and the .bind() method returns a new function with 'Hello' and 'Maria' bound to the first and second parameter. That looks a little better, doesn’t it?

However, what if we only have the greetings and location parameter? How can we bind the first and last parameter? The Function.prototype.bind() method can’t do it, since it only binds the parameters in sequence (a, b and c). It can’t bind a and c together and skip b.

Let’s try using curry instead. Although we can achieve the same result as a partial application with curry, curry is not equal to the partial application.

Curry is a function that, given a function with arity N, returns a function with arity N-1. With this definition, we can write a solution like in the following code snippet:

const curriedSayHi = greetings =>
location =>
name => sayHi(greetings, name, location);

const sayHiTo = curriedSayHi('Hello')( 'Portugal');

getName()
.then(name => sayHiTo(name));

Yes, it works, but we need to write a different curry function whenever we need another parameter order. Moreover, it may be hard to predict whether the next call will bind the next parameter or call the target function. Is there another way to do it without all of this boilerplate code?

Alternatively, we can use the arrow function for the same purpose.

const sayHiTo = name => sayHi('Hello', name, 'Portugal');

getName()
.then(name => sayHiTo(name));

As you can see, there are many ways to achieve a partial application in ECMAScript but none of them has been standardised. These use cases are the baseline problem that the partial application proposal tries to address.

The Partial application proposal

The Partial application proposal creates two new tokens to be placed inside a function call, which allow us to partially apply arguments to a specific function. The ? (question mark) token for binding a single parameter and the ... (ellipsis) token for multiple parameters. Following these definitions, we can revisit our previous example and rewrite it using the partial application proposal.

const sayHiTo = curriedSayHi('Hello', ?, 'Portugal');

getName()
.then(name => sayHiTo(name));

,p>As you can see here, it becomes clear how to bind an arbitrary parameter, without adding code to handle that. Also, just by reading the code, it’s trivial to figure out how much parameter is bound and which one should be provided.

The ellipsis token

In this proposal, there is another token mentioned, the ellipsis token (...). It works by spreading the parameters into a specific position. Does it sound complicated? Let’s see it in action in the code.

Let’s suppose we need to take the biggest number of a series, but if all numbers are smaller than zero, we return zero.

const maxGreaterThanZero = (...numbers) =>
Math.max(0, Math.max(...numbers));

maxGreaterThanZero(1, 3, 5, 7); // 7
maxGreaterThanZero(-1, -3); // 0

The code above is one possible solution to this problem. To achieve the goal, we nested two Math.max methods, where the inner one returns the biggest number given the numbers parameter. If the inner method returns a number smaller than zero, the outer method makes sure that zero is returned. Using the ellipsis token, we can achieve the same result with the following code.

const maxGreaterThanZero = Math.max(0, ...);

maxGreaterThanZero(1, 3, 5, 7); // 7
maxGreaterThanZero(-1, -3); // 0

Super simple, isn’t it? Let’s check how this feature may work together with the pipeline operator.

Do you remember when we spoke about the F# style with await? We pointed out that the syntax overhead of the arrow function wasn’t covered there. But, with the partial application, we can rewrite that code like this.

document.getElementById('user-input')
|> sanitize
|> await
|> textualizeNumbers(?, ['0', '1'])

/*                     ^ this will return a function which expects

*                     ? token to be the subject from the previous step

*/
|> console.log

The Partial application is in stage 1, and the champion is Ron Buckton.

Conclusion

As you can see, many aspects of these proposals are still unsettled. The adoption of one may reshape the syntax or semantics of another feature proposal or even delete it.

So, should you use them in production? Not yet. However, I encourage you to try them out, check if they solve valid problems, add a star to the proposal repository you would like to support, and add comments for things that are unclear or even open an issue. Only with your feedback the committee can understand the developer’s pain and make ECMAScript the language we love.

Subscribe to our monthly Heart Internet newsletter, filled with the latest articles about web design, development, building your business, and exclusive offers.

Subscribe now!

Comments

Please remember that all comments are moderated and any links you paste in your comment will remain as plain text. If your comment looks like spam it will be deleted. We're looking forward to answering your questions and hearing your comments and opinions!

Leave a reply

Comments are closed.

Drop us a line 0330 660 0255 or email sales@heartinternet.uk