1. A brief introduction to the ES world
ES is short for ECMAScript, a standard that JavaScript is built on top of. Starting from 2015, the TC-39 (“a group of JavaScript developers, implementers, and more, collaborating with the community to maintain and evolve the definition of JavaScript”) (1) added new features to the JavaScript Language every year, helping us to write cleaner code and reducing as much as possible the amount of bugs in projects.
Once a feature reaches stage 4 (there are 4 stages: Proposal, Draft, Candidate and Finished), it will be a part of the new ECMAScript release, and it’s the duty of the browser to implement that feature.
2. ES2020 Features:
At the time of writing this article, some of the features have already been implemented. For example, Chrome 80 added support for the Nullish operator. Before using any of these new features, I recommend taking a look at https://caniuse.com/, to check if the features you want to use are available on your targeted browser.
a. Nullish Coalescing Operator (??)
const falsyTypes = { emptyString: '', zeroNum: 0, falseBoolean: false, nullValue: null, }
Sometimes we want to assign some sort of default value to our variables, but currently, in order to do that, we can only use the “||” operator:
const undefinedValue = falsyTypes.undefinedValue || 'my default' console.log(undefinedValue) // 'my default' const nullValue = falsyTypes.nullValue || 'my default' console.log(nullValue) // 'my default'
const stringVal = falsyTypes.emptyString || 'my default' console.log(stringVal) // 'my default' const booleanVal = falsyTypes.falseBoolean || 'my default' console.log(booleanVal) // 'my default' const numberVal= falsyTypes.zeroNum || 'my default' console.log(numberVal) // 'my default'
As you can see, there is a difference in the behavior of the ‘||’ operator regarding different data types - inside the box, the comparison made is equivalent to the ‘==’, with the values being converted to truthy or falsy. So, in order to get rid of this data conversion, we can use the ‘??’ operator, assigning these default values just in case of ‘undefined’ or ‘null’.
const stringVal = falsyTypes.emptyString ?? 'my default' console.log(stringVal) // ' ' const booleanVal = falsyTypes.falseBoolean ?? 'my default' console.log(booleanVal) // 'false’ const numberVal= falsyTypes.zeroNum ?? 'my default' console.log(numberVal) // '0'
b. Optional Chaining Operator (Also known as the Elvis operator)
The Optional Chaining Operator lets us read values from deep chained properties of objects. If one of those does not exist, the checking will stop when the referenced property is ‘undefined’ or ‘null’ and it will be returned as ‘undefined’.
Here are some examples of how we could do that property checking:
const nestedObject = { firstProp: { secondProp: { thirdProp: { nestedProp: 3 } } } }; // Check if the propriety exits using if statement. if ( nestedObject && nestedObject.firstProp && nestedObject.firstProp.secondProp && nestedObject.firstProp.secondProp.thirdProp && nestedObject.firstProp.secondProp.thirdProp.nestedProp ) { // do your stuff with the ‘nestedProp’. const validPropriety = nestedObject.firstProp.secondProp.thirdProp.nestedProp console.log(validPropriety) // 3 } // Check if the option exists by validating all proprieties between const validPropriety = nestedObject && nestedObject.firstProp && nestedObject.firstProp.secondProp && nestedObject.firstProp.secondProp.thirdProp && nestedObject.firstProp.secondProp.thirdProp.nestedProp console.log(validPropriety) // 3
This is quite ugly and messy, so instead, you can use the optional chaining operator (?.) and do something like this:
const checkedProp= nestedObject?.firstProp?.secondProp?.thirdProp?.nestedProp console.log(checkedProp) // 5
In this case, if any of the nested props does not exist, the value of ‘checkedProp’ will be undefined.
const checkedProp = nestedObject?.inexistentProp?.secondProp?.thirdProp?.nestedProp console.log(checkedProp) // ‘undefined’
This can also be used together with the “??” operator, to give your variables default values in case one of the properties does not exist.
const checkedProp= nestedObject?.inexistentProp?.secondProp ?? 0 console.log(checkedProp) // 0
c. Promise.allSettled
Let’s initialize three new Promises, two of which resolve the response and one that rejects it.
const resolvedPromise1 = new Promise(resolve => resolve("Success")); const rejectedPromise = new Promise((_,reject )=> reject("Failed")); const resolvedPromise2 = new Promise(resolve => resolve("Success"));
Here we are going to make a comparison between Promise.all() (ES 2015) and Promise.allSettled(ES 2020), to see their different behaviours.
Promise.all():
Promise.all([resolvedPromise1, rejectedPromise, resolvedPromise2]) .then(fianlRes => console.log("Success response: ", fianlRes)) .catch(err => console.log("Failed response:", err));
Promise.allSetteld():
Promise.allSettled([resolvedPromise1, rejectedPromise, resolvedPromise2]) .then(fianlRes => console.log("Success response: ", fianlRes)) .catch(err => console.log("Failed response:", err));
As you can see, Promise.allSetteld() comes in handy when you do not care about the response that comes from your promises, and you just want to execute code after everything is done. In the case of .all, if one of the promises is rejected, the entire execution will be stopped and the response will come into .catch().
d. Dynamic import
Let’s say you have a button that generates a pdf copy of your current page. Until now, you haven’t been able to import anything on a specific event, with the files being mandatorily injected at the top of the document.
The code structure is fixed because the compiler needs to gather information about imports and remove unused exports (“Tree-Shaken”), all of that information being fixed at the start of the document.
However, if the user never presses that “Create pdf” button, we just end up with an unused import and a bundle size that has been increased unnecessarily. Let’s dive into dynamic imports and see how this problem can be solved.
For the sake of popularity, the given example is provided in React, but it also applies to every environment that supports dynamic imports.
// pdf.js export function generatePDF() { // pdf generator code }
// our document importLibrary(){ const {generatePdf} = await import('./pdf.js'); generatePDF(); } render(){ return ( <div> {/* ... */} <button onclick={()=> this.importLibrary()}>Generate pdf</button> {/* ... */} </div> ); }
If pdf.js has a default export:
// pdf.js export default function { // pdf generator code }
// our document importLibrary(){ const dinamicImport = await import('./pdf.js'); const generatePDF = dinamicImport.default; generatePDF(); }
e. globalThis
Over time, JavaScript has evolved, and because of this, it is now being used in all kinds of environments, thus creating a problem with accessing the global object, depending on the running env.
For example:
- Web: window, self or frames;
- NodeJs: global;
- Web Workers: self.
Let’s create a function that returns the global object from the current working environment:
const getGlobalObj = () => { if (typeof global !== "undefined") return global; if (typeof self !== "undefined") return self; if (typeof window !== "undefined") return window; throw new Error("Can’t get the global obj."); }; const currentGlobalObj = getGlobalObj()
Instead of that, now you can do the following:
const getGlobalObj = () => globalThis
f. Private class variables
When it comes to JavaScript Classes, ES2020 introduces a little change in the way we can make data from a class private, just by using the hash symbol:
class Developer { name = "Steve"; // public field #projectsInvolved = 5; // private field getNumberOfProjectsInvolved(){ return this.#projectsInvolved } getName(){ return this.name } } const dev = new Developer() console.log(dev.getName()) // "Steve" console.log(dev.name) // "Steve" console.log(dev.getNumberOfProjectsInvolved()) // 5 console.log(dev.#projectsInvolved) // Uncaught SyntaxError: Private field '#projectsInvolved' must be declared in an enclosing class
3. Conclusion
JavaScript is a continuously evolving language, a quality that has made Web Development as awesome as it is today. As you can see in the examples provided in this article, these new features are great regarding code optimization and simplicity. If you really want to use one of them, but the browser does not support it yet, you can always use Babel or other JavaScript transpilers.
Want to take a look at and follow up on the complete list of ECMAScript proposals? Visit this link.
4. Sources
- https://tc39.es/ (1)
- proposals/finished-proposals.md at master · tc39/proposals
- Professional JavaScript for Web Developers 4th Edition - Matt Frisbie
- Nullish operator
- Dynamic Import