Categories
JavaScript

Great New Features in ES2018 You May Have Missed

Spread the love

Ever since ES2015 was released, which was a great leap forward in itself, JavaScript has been improving at a fast pace. Every year since then, new features have been added to JavaScript specifications.

Features like new syntax and new methods for building in JavaScript have been added consistently. In ES2016 and 2017, The Object object had methods like Object.values and Object.entries added.

String methods like padStart and padEnd were added in ES2017. async and await, which is a shorthand syntax for chaining promises, were also added in ES2017.

The includes methods for arrays were added in ES2016. ES2018 was another release with lots of new features. With ES2018, the spread syntax is now available for object literals. Rest parameters were also added.

The for await...of loop, which is a loop syntax iterating through promises sequentially, was also added. The SharedArrayBuffer object was added for representing raw binary data that cannot become detached.

A finally function was also added to the Promise object.


Spread Operator in Objects

The spread syntax works by copying the values of the original array, and then inserting them into another array or putting them in the order they appeared in the array as the list of arguments in a function in the same order.

When the spread operator is used with objects, the key-value pairs appear in the same order they appeared in the original object.

With ES2018, the spread operator works with object literals. Then key-value pairs of an object can be inserted into another object with the spread operator.

If there are two objects with the same key that the spread operator is applied to in the same object, the one that’s inserted later will overwrite the one that’s inserted earlier.

For example, if we have the following:

let obj1 = {foo: 'bar', a: 1};  
let obj2 = {foo: 'baz', b: 1};  
let obj3 = {...obj1, ...obj2 }

Then we get {foo: “baz”, a: 1, b: 1} as the value of obj3 because obj1 is spread before obj2. They both have foo as a key in the object.

First foo: 'bar' is inserted by the spread operator to obj3. Then foo: 'baz' overwrites the value of foo after obj2 is merged in since it has the same key, foo, but was inserted later.

This is great for merging objects as we don’t have to loop through the keys and put in the values, which is much more than one line of code.

One thing to note is that we can’t mix the spread operator between regular objects and iterable objects. For example, we will get TypeError if we write the following:

let obj = {foo: 'bar'};  
let array = [...obj];

Rest Operator

The rest operator is a JavaScript operator where we can store an indefinite number of arguments in a function as an array. It looks exactly like the spread operator, which lets us spread entries of an array or a list of key-value pairs of an object into another object.

For example, if we have a function that has a long list of arguments, we can use the rest operator to shorten the list of parameters in a function.

The rest operator is used with the following syntax:

const fn = (a,b,..restOfTheArgs) => {...}

Where restOfTheArgs is an array with the list of arguments other than the first two.

For example, if we want to write a sum function that takes an indefinite list of numbers as arguments and sum up the numbers, we can write:

const sum = (a,b,...otherNums) => {  
  return a + b + otherNums.reduce((x,y)=>x+y, 0);  
}

As we can see, this is very handy for functions that have any list of arguments. Before we had this, we had to use the arguments object available in functions to get a list of arguments. This is not ideal as we allow them to pass in anything into the arguments.

With the rest operator, we have the best of both worlds. We can have some fixed parameters, while the rest stay flexible. This makes functions more flexible than functions with a fixed number of parameters, while having some flexibility of functions that take an indefinite number of arguments.

The arguments object has all the arguments of the function. Also, it’s not a real array, so array functions aren’t available to them. It’s just an object with indexes and keys to denote the argument’s positions.

Methods like sort, map, forEach, or pop cannot be run on the argument’s object. It also has other properties. This creates confusion for programmers. The arguments that are converted to an array with the rest operator do not have these issues, as they are real arrays.

To call the sum function we wrote, we can write:

const result = sum(1,2,3,4,5,6,7,8,9,10);

The result will be 55, since we summed up all the arguments together. otherNums is an array with all the numbers other than 1 and 2.

We can also use the rest operator to destructure a list of arguments into a list of variables. This means that we can convert a list of parameters into an array with the spread operator, and then decompose the array of arguments into a list of variables.

This is very useful as we can get the entries of the array that’s operated on by the rest operator and convert them to named variables. For example, we can write:

const sum = (a,b,...[c,d,e])=> a+b+c+d+e;

This way, we can use the rest operator, but limit the number of arguments that your function accepts. We take the function parameters a and b, and we also take c, d, and e as parameters.

However, it’s probably clearer without using the rest operator since all the parameters are fixed, we can just list the parameters directly.


for await...of

The for await...of loop allows us to create a loop that iterates over a list of promises as well as over the entries of a normal iterables.

It works with iterable objects like arrays, string, argument object, NodeList object, TypedArray, Map, Set, and user-defined synchronous and asynchronous iterable objects.

To use it, we write:

for await (let variable of iterable) {  
  // run code on variable
}

The variable may be declared with const, let, or var, and the iterable is the iterable objects that we are iterating over. We can iterate over asynchronous iterable objects like the following:

const asynNums = {  
  [Symbol.asyncIterator]() {  
    return {  
      i: 6,  
      next() {  
        if (this.i < 20) {  
          return Promise.resolve({  
            value: this.i++,  
            done: false  
          });  
        }
        return Promise.resolve({  
          done: true  
        });  
      }  
    };  
  }  
};

(async () => {  
  for await (let num of asynNums) {  
    console.log(num);  
  }  
})();

We should see 6 to 19 logged. It also works with async generators:

async function* asynNumGenerator() {  
  var i = 6;  
  while (i < 20) {  
    yield i++;  
  }  
}

(async () => {  
  for await (let num of asynNumGenerator()) {  
    console.log(num);  
  }  
})();

We should see the same thing logged. It also works great with promises:

const arr = Array.from({  
  length: 20  
}, (v, i) => i)let promises = [];

for (let num of arr) {  
  const promise = new Promise((resolve, reject) => {  
    setTimeout(() => {  
      resolve(num);  
    }, 100)  
  })  
  promises.push(promise);  
}

(async () => {  
  for await (let num of promises) {  
    console.log(num);  
  }  
})();

As we can see, if we run the code above, we should see 0 to 19 logged sequentially, which means that promises were iterated through in sequence. This is very handy as we never have anything that can iterate through asynchronous code before this loop syntax was introduced.


Promise.finally()

The finally function is added to the Promise object which runs when the promise is settled. That means it runs whenever the promise is fulfilled or rejected.

It takes a callback function that runs whenever the promise is settled. This lets us run code regardless of how a promise ends. Also, this means that we no longer have to duplicate code in the callbacks for then and catch functions.

To use the finally function, we use it as the following:

promise  
.finally(()=>{  
  // run code that when promise is settled  
})

The finally is useful is we want to run code whenever a promise ends like cleanup code or processing after the code ends.

It’s very similar to calling .then(onFinally, onFinally). However, we do not have to declare it twice to make a variable for it and pass it in twice. The callback for the finally function doesn’t take any argument, as there’s no way to determine the status of the promise beforehand.

It’s used for cases when we don’t know what will happen to the promise, so there’s no need to provide any argument. If a promise is fulfilled then the resolved value will be intact after calling the finally function.

For example, if we have:

Promise.resolve(3).then(() => {}, () => {})

This will resolve to undefined, but if we have:

Promise.resolve(3).finally(() => {})

The code above will resolve to 3. Similarly, if the promise if rejected, we get:

Promise.reject(5).finally(() => {})

This will be rejected with the value 5.


SharedArrayBuffer

The SharedArrayBuffer is an object that’s used to represent a fixed-length raw binary data buffer.

It’s similar to the ArrayBuffer object, but we can use it to create views on shared memory. It can’t become detached, unlike an ArrayBuffer. The constructor of it takes the length of the buffer as an argument.

The length is the size in byte for the array buffer to create. It returns a SharedArrayBuffer of the specified size with contents initialized to 0.

Shared memory can be created and updated by multiple workers on the main thread. However, this may take a while until the changes are propagated to all contexts.


Conclusion

With ES2018, we have more handy features to help developers develop JavaScript apps.

The highlights include the spread operator for objects, which let us copy key-value pairs to other objects and the rest operator to let us pass optional arguments.

The for await...of loop lets us iterate through collections of asynchronous code which could never easily be done before.

Leave a Reply

Your email address will not be published. Required fields are marked *