Vital Concepts for Modern JS Developers

Share

This resource contains a collection of modern, vital JavaScript concepts provided by our Toptal network members.

Still the de facto language of the web, JavaScript has a long and fragmented history. Key players have made concerted efforts in recent years to have JavaScript conform to a standard known as ECMA-262 or ECMAScript.

In mid-2019, major milestones like ECMAScript 2015 (ES2015, formerly ES6) are still not completely implemented by any front- or back-end JavaScript engines.

Since ES2015, the standards group has decided to release specifications once a year, and ES2016, ES2017, ES2018, and ES2019 all add interesting features. But it can be tough to keep on top of what’s useable and useful in JavaScript right now—whether it’s cutting-edge magic or tried-and-true techniques.

Hence this community-driven project, bringing together expert advice on the most important advanced concepts that modern JS developers should know. We welcome your contributions and feedback.

Closures and Higher-order Functions

Closures and higher-order functions are two important concepts in JavaScript, and ones that are closely related. Let’s find out how.

Closures

When we create a function inside another function in JavaScript, the inner function has access to the scope of the outer function as well as its own.

In the code example below, the count function is able to access total, which is defined in the outer scope within tallyCounter.

function tallyCounter() {
  let total = 0;

  function count() {
    total++;
    return total;
  }
}

Here, the count function is a closure, and continues to have access to the total variable even after tallyCounter has finished executing. This is due to lexical scoping in JavaScript, allowing functions to access variables even when they’re called from outside of the scope in which they were initially created.

The above code isn’t very useful, though, because we can’t call count from the outside. It was defined in the tallyCounter scope, and it’s not available to the outside:

function tallyCounter() {
  let total = 0;

  function count() {
    total++;
    return total;
  }
}

console.log(count()); // Error: count is not defined

In order for our closure to become useful, we need to return it. That way, we can assign it to a local variable outside of tallyCounter, and start using it:

function tallyCounter() {
  let total = 0;

  return function count() {
    total++;
    return total;
  }
}

let count = tallyCounter();

console.log(count()); // log: 1
console.log(count()); // log: 2
console.log(count()); // log: 3

In the example above, we don’t have access to the total variable from outside of the tallyCounter function. We’re able to call count and increment total from the outside but we cannot access the value directly. As such, closures are often used in JavaScript to protect variables from outside manipulation, or to hide implementation details.

Another common use case for closures is deferring code execution. Callbacks in JavaScript are a prime example of this:

function logTheDateLater(delay) {
  let date = new Date();

  function logDate() {
    console.log(date);
  }
  
  setTimeout(logDate, delay);
}

Here, we’re calling setTimeout with a callback function that will output the current date and time—as of calling the outer function—but after some delay. Internally, setTimeout will call our callback function after the delay, without any knowledge of what it does or how it works. It also does not have access to the date variable. Because our callback function is a closure, it has access to date in its lexical scope, and it can log it to our console.

Higher-order Functions

Both tallyCounter and setTimeout are what we call higher-order functions in JavaScript. These are functions that either return another function, or accept one or more function(s) as input parameters.

They are called higher-order because they are more concerned with higher-level concepts than lower-level details. As we mentioned before, callbacks are a great example of that:

function helloToptal() {
  console.log('Hello Totpal');
}

setTimeout(helloToptal, 1000);

Here, setTimeout is a higher-order function because it accepts a callback function, and will execute it after a given delay. It does not need to know what that JavaScript function does.

Now, let’s say we want to make sure that we don’t log "Hello Toptal" more than once per second. Here’s a higher order function that will help us achieve that:

function throttle(fn, milliseconds) {
  let lastCallTime;

  return function() {
    let nowTime = Date.now();

    if(!lastCallTime || lastCallTime < nowTime - milliseconds) {
      lastCallTime = nowTime;
      fn();
    }
  };
}

The throttle function accepts a function parameter, and returns a new, throttled function to the caller. We can now use it to create a throttled version of our helloToptal function:

let helloToptalThrottled = throttle(helloToptal, 1000);
setInterval(helloToptalThrottled, 10);

You’ll notice that, despite the 10ms interval, calls to helloToptal are throttled and "Hello Totpal" will only be logged once per second.

Higher-order functions like throttle allow us to compose our JavaScript applications from smaller, single-responsibility units of code that are easier to test, reusable, and certainly a lot more maintainable.

Contributors

Merott Movahedi

Freelance JavaScript Consultant
United Kingdom

Merott is a full-stack developer with a strong interest in the front-end space. He is proficient in JavaScript and has a good grasp of its core concepts and best practices. He is also strong in JavaScript frameworks, especially Angular. In addition to web development, he has an interest in mobile apps, on which he spends a substantial amount of time. Merott absolutely loves to use his programming skills to automate and streamline mundane tasks.

Show More

Get the latest developer updates from Toptal.

Subscription implies consent to our privacy policy

The Rest and Spread Operators

ES6 adds new syntax for doing rest and spread, but the operator looks like ... in both cases. Rest was already doable before ES6, but it required a lot more code. It also didn’t look as pretty.

To illustrate this, let’s write a function called getUser that takes an object containing either a userId or username property, in ES5:

function omit() {
    if (arguments.length < 2) {
        throw new Error('omit requires at least 2 arguments')
    }

    var obj = arguments[0],
        keys = Array.prototype.slice.call(arguments, 1)

    var ret = {}

    for (var key in obj) {
        if (obj.hasOwnProperty(key) && keys.indexOf(key) === -1) {
            ret[key] = obj[key]
        }
    }

    return ret
}

function getUser(params) {
    var userId = params.userId,
        username = params.username,
        args = omit(params, 'userId', 'username')
    
    if (typeof userId !== 'undefined') {
        return getUserById(userId, args)
    }

    if (typeof username !== 'undefined') {
        return getUserByUsername(username, args)
    }

    throw new Error('either userId or username needs to be passed')
}

Note that we had to write our own omit function to not include certain keys. We also had to duplicate the userId and username fields, once in the var definition and once in our call to omit. Let’s take a look at the same code written using the ES6 rest operator:

function getUser(params) {
    const { userId, username, ...args } = params
    
    if (typeof userId !== 'undefined') {
        return getUserById(userId, args)
    }

    if (typeof username !== 'undefined') {
        return getUserByUsername(username, args)
    }

    throw new Error('either userId or username needs to be passed')
}

As you can see, this is much easier to read and understand what is going on at a first glance, and there was no need for the extra omit helper function.


The ES6 spread operator is also very helpful. For example, it can be used to spread multiple sources into a destination:

function mergeThreeObjects(obj1, obj2, obj3) {
    return { ...obj1, ...obj2, ...obj3 }
}

console.log(mergeThreeObjects({ x: 1 }, { y: 1 }, { y: 2, z: 3 })

This logs { x: 1, y: 2, z: 3 }. Note that later keys will overwrite earlier keys, much like Object.assign() in ES5. (See how the y property in the third argument overwrote the y property in the second argument.)

There is also syntax to do the reverse:

function addEmailToUser(email, user) {
    return { email, ...user }
}

Here, this addEmailToUser function copies the top-level attributes of user to an object that has only the email property. Note that this is a shallow copy. The equivalent code in ES5 is:

function addEmailToUser(email, user) {
    return Object.assign({ email: email }, user)
}

The ES6 rest/spread operators also work with arrays, which can make older syntax such as that using Array.prototype.concat and Array.prototype.slice a lot more readable:

ES5:

var list = [0, 1, 2, 3, 4, 5, 6]
var first = list[0]
var rest = list.slice(1)
console.log(first, rest)

ES6:

const [ first, ...rest ] = [0, 1, 2, 3, 4, 5, 6]
console.log(first, rest)

These both log 0, [1, 2, 3, 4, 5, 6].

The spread operator with arrays can also replace the old ES5 way of using Array.concat to combine arrays:

ES5:

var others = [1, 2, 3, 4]
var arr = [1, 2].concat(others)

ES6:

const others = [1, 2, 3, 4]
const arr = [1, 2, ...others] // [1, 2, 1, 2, 3, 4]

As you can see, ES6’s rest and spread operators sometimes just provide syntactic sugar, but other times are much more powerful than that. Enjoy!

Contributors

David Xu

Freelance JavaScript Consultant
United States

David has taken several mobile apps from an idea to millions of users all over the world, as the Chief Architect of Castle Global, Inc and technical lead in other companies. He has been programming since the age 9 and has won medals in various competitive programming competitions, from the Australian Informatics Olympiad to ACM-ICPC. David has invaluable experience in all areas technical, from architecture and design to engineering and DevOps.

Show More

Understanding Async/Await

Understanding asynchronous programming is essential to modern software development and the creation of progressive web applications. Without a good understanding of how it works, it can be very easy to write code that turns into an unmanageable mess, especially as client expectations grow and change throughout the course of a project.

Functionality changes can also demand that code be converted from synchronous to asynchronous. These are equally important in server-side code and client-side code.

Thankfully, in recent years, JavaScript has greatly simplified the process, especially with the availability of async and await.

Overview of Promises

JavaScript promises are simply a way of attaching callback functions to some asynchronous event, such as a call to fetch records from a database. Since the database server won’t necessarily respond immediately, we would need to attach a callback to wait for the response rather than block the rest of our application from continuing.

Promises allow us to chain events together recursively or iteratively and to use the resulting data at each step. It’s a very powerful tool, but without a defining structure, code can look very unreadable pretty quickly. The example given below reads employee records from a database and then dumps them to the console:

db.manyOrNone ('select * from employee')
.then (console.log); 

The first line issues the call to the database and returns a Promise, which can be thought of as a thread with two callback functions attached, resolve and reject—although it doesn’t necessarily have to be implemented internally using threads. The code in this thread will continue to run without blocking any other part of our code, and when it’s done, the resolve function will be called, passing any resulting data to it. If any error is encountered, the reject function will be called instead, with an error code being passed to it.

In the second line of code, we’re calling the .then() method on the returned Promise object, which attaches the handler for the resolve part of our promise. In this case, we’re just attaching console.log as the handler, which will output the returned data to the console. But inside our callback function, we can do whatever we want with the data, knowing that it will be called only when the database fetch has been completed.

Developers familiar with jQuery are used to this same type of pattern, although in that case, a callback function is usually passed as a parameter or a member of a settings object.

This code is very simple and readable, but as we’ll see in the next section, things can easily get pretty messy.

The Problem

There are certain cases where writing promise-based code can be quite complicated. One of them is promises that are based on conditions. Let’s say we have an employee object, and we want to query information about their medical coverage, but only if they’re employed full-time. We might write code like this:

getMedicalCoverage(employee)
.then (console.log);

function getMedicalCoverage(employee) {
   if (!employee.isFullTime) {
      return (Promise.resolve (null));
   }
   else {
      return (db.manyOrNone ('select * from coverage where employee_id=' + employee.id));
   }
}

In this example, the getMedicalCoverage function will return a Promise whether the employee is full-time or not. If the employee isn’t full-time, it returns a Promise that gets resolved immediately with a value of null. If they are full-time, it performs a database query that gets resolved after the data is fetched.

Data fetching that is based on a condition typically requires us to create a promise either way, and that’s code we have to write every single time we encounter this scenario. These code patterns can be made much simpler to manage by using async and await, so let’s start with a description of how it works.

Async/Await to the Rescue

In modern JavaScript, async and await are used to handle this process in a much more concise way. First, let’s start by building an understanding of what async does. When we write a function that is intended to run without blocking the rest of our code and/or interface, we’d define it as an async function, using the following syntax:

async function readEmployees () {
   var employees = ["Elsa", "Anna", "Kristoff"];
   return (employees);
}

And here’s the syntax in case we’re using ES6 arrow functions:

const readEmployees = async () => {
   var employees = ["Elsa", "Anna", "Kristoff"];
   return (employees);
};

Never mind the fact that this function is simply returning an array of predefined strings for now. The point is that we’ve defined it as an asynchronous function, which means that JavaScript automatically wraps it in a Promise. This causes a Promise to be created, with this function as the body of that Promise. When we return a value from the function, it acts as a resolve for the Promise.

Now, let’s look at an example of how to call an asynchronous function:

var employees = await readEmployees ();

It’s really that simple! We just insert the await keyword before the function call itself. No matter what goes on under the hood (API calls, database calls, etc.), the calling environment doesn’t need to be concerned with anything but calling the function and using the return value.

Keep in mind that we can’t use await unless it’s inside an asynchronous function. Defining a function as async exposes the await keyword inside its scope. The idea is that our top-level function that kicks off a whole process needs to be treated as a regular promise that has a .then() handler waiting for it to finish, but the internal steps can be blocking. Here’s an example that demonstrates the whole process:

const readEmployees = async () => {
   var employees = await db.manyOrNone ('select * from employee');
   for (let i=0; iShow More

Using Binary and Ternary Operators to Increase Readability and Reduce Your Code

There are many developers who have a year or two of experience and ask themselves the following: How do I become a better developer? How do I go from being a middle- or beginner-level developer to the next level? The key is experience, but what is important about it is not time but what do you do in that time. Valuable experience is all about the knowledge you have and how well you apply it. This means you can hack the amount of experience by learning and putting into practice as many ideas as possible.

In this article you’ll learn some tricks that are not common for developers to learn. But most importantly, you’ll learn how to apply these tricks to real-world examples. This will help you grow your skills faster.

Code Readability

A good coder shouldn’t limit themselves to only writing code that works; they should write code that can stand the test of time. As you grow as a developer, you start to realize that, often, you won’t be there for any changes the code will need. The reasons are many, like switching jobs, or simply having to focus on other features while another coder works on your previous code. That’s why writing clear code becomes as important as the functionality of it.

You may have written a piece of code five years ago, and even to yourself now it seems it was written by an stranger. So it would be irresponsible to write code that sacrifices readability just for the sake of proving you know some weird hack of the language.

The tricks you’re about to learn are going to compress the code you’re writing under some circumstances; however, you shouldn’t abuse these tricks. Increasing readability is the actual objective of these tricks, as I’ll help you express the same ideas with less lines. Writing less code results in code that is more manageable, which you’ll appreciate as you jump to more complex and bigger codebases.

Overview

Because I want to focus on some weird but practical tricks, we’ll be looking at these operators:

  • && (the and operator)
  • || (the or operator)
  • % (the mod operator)
  • ? : (the ternary operators)

The And Operator

This operator might not be a stranger—you might have used it in the past to verify that multiple conditions are met. Like the following example:

if (bankAccount.amount > item.price && item.inventory >= shippingItem.qty) {
  buy();
}

But when we use the && operator, the result isn’t necessarily true or false—it’s actually the value of the last condition that has been met. So we get a true value only in the case that all conditions are be considered truthy. Truthy is easier to define as the opposite of what is falsy. Falsy is a value treated as false for JavaScript, and that includes the following values:

  • null
  • undefined
  • false
  • 0
  • ""
  • NaN

This will give you an idea of how this works:

// 3 and 5 are not falsy so both conditions are truthy
3 && 5 // result: 5

This might look like a very useless thing to know; however, this is very powerful when you want to check if a value exists under an object which itself might or might not exist.

Let’s say you’re creating a component which has a config object in which you might set a style object to set the fontSize. And let’s make the default fontSize 14 in case it isn’t set on the config object.

The code to do that might look like the following:

var config = {
  style: {
    fontSize: 12
  }
};
var fontSize = 14;

if (config.style) {
  if (config.style.fontSize) {
    fontSize = config.style.fontSize;
  }
}

After applying the && operator and the || operator (which you’ll learn next) it reduces to:

var config = {
  style: {
    fontSize: 12
  }
};
var fontSize = (config.style && config.style.fontSize) || 14;

Another powerful trick of this operator is that the second condition will only be executed if the first condition is truthy.

In our next example, let’s imagine your form component may have a callback function when it’s submitted, but this callback may or not exist. To call it, you might do the following:

if (this.onSubmit) {
  this.onSubmit();
}

However, with the && operator, this gets reduced to:

this.onSubmit && this.onSubmit();

You don’t care about the result, you just care that the function gets executed in the case where it exists.

The Or Operator

Again, you might have used it under conditional statements, but also again, this operator can return something different from true or false. It returns the first truthy part.

// 24 and 45 are both truthy
24 || 45 // returns 24

This is very helpful to set default values on the code, but please note that this only works if a falsy value isn’t valid input. Let’s say we are working on a food platform and want to know how many ice cream cones a client wants. However, the default will be 1. Using this operator, you’ll have the following:

var qty = order.ice_cream || 1;

So if the client orders one or more ice cream cones, the result will be correct. But if the client selects zero, the result will be one—please beware of this! In the case we explored before—about fontSize—it works perfectly since zero isn’t expected as valid input.

This || operator combined with the && operator can verify the existence of an object, get the value, and in the case of non-existence, default to a predefined value.

The Conditional (Ternary) Operator ?:

Have you ever written an if statement whose only purpose was to set a value on a variable depending if a condition is met? Let’s say, for example, you want to change the background color of a page depending on whether the user has a premium subscription. Your code might look like this:

if (user.premium){
	this.headerBar.style.backgroundColor = colors.GOLD;
} else {
this.headerBar.style.backgroundColor = colors.BLUE;
}

There will be times when you’re facing this situation. Fortunately, the ternary operator can help you express the same thing in a more compact way. The operator has three parts:

  1. The condition
  2. Code that is returned if the condition is met
  3. Code that is returned if the condition is not met

So:

if (a) {
  b;
} else {
  c;
}

Will transform into:

a ? b : c

Back to our previous example using the ternary operator, the code can be rewritten as:

this.headerBar.style.backgroundColor = user.premium ? Colors.GOLD : Colors.BLUE;

Another use is formatting text depending on certain conditions, like if we have a price and we want to display it as a dollar amount or cents:

var priceText = (price < 1) ? ("$" + price.toFixed(2)) : (price * 100 + "¢");

The % Operator

This one is a very strange operator, as it returns the remainder of a division operation. Let’s start by understanding how it works. Say you do 5 % 3: If you divide 5/3 the result is 1 if you don’t calculate any decimals; your remainder will be 2. Seems like it won’t have a lot of use until you start dealing with time.

Let’s say for example you’re building a stopwatch. If you search on the internet, you’ll see that most of the code out there tries to add the time that has passed by adding each second that has passed. This results in more complexity than needed.

We only need to know how much time has passed. Let’s say the current time is 150,678 ms, i.e., the time that’s passed since the stopwatch began to run. You’ll need to process that amount into minutes and seconds, which would be 02:30. If you want to display how many minutes and seconds have passed, you can do the following:

var seconds = (currentTime / 1000); // get the total number of seconds
seconds = (seconds | 0); // take away the decimal part

// this will divide by 60 which will be the minutes and
// the remainder is the number of leftover seconds
seconds = (seconds % 60); 

// using same approach to calculate minutes:
var minutes = ((currentTime / (60 * 1000)) | 0) % 60;

Another use of the % operator is when dealing with next and previous functions that cycle when they reach the last item. Say you have a slideshow and the way you render a slide is by calling show(slideIndex), and you have an array with all the slides. Usually your next function will look like this:

function next() {
  if (this.currentIndex + 1 < this.slides.length) {
    this.show(this.currentIndex + 1);
  } else {
    this.show(0);
  }
}

Using the % operator, that will be:

function next(){
  this.show((this.currentIndex + 1) % this.slides.length);
}

Using All These Tricks Together

Using all that we have covered here, we can build a simple stopwatch with the following code in less than 30 lines of code:

function StopWatch(config){
  // create config object in case none was sent
  config = config || {}
  // Read fontsize from config or set default to 12
  var fontSize = (config.style && config.style.fontSize) || 12;
  // Read color from config or set default to #000
  var color = (config.style && config.style.color) || '#000';

  var element = document.createElement('div');
  // add the color and font size to the div 
  //  that will hold our stopwatch
  element.style.color = color;
  element.style.fontSize = fontSize;

  // Save when the stopwatch starts running
  var startTime = new Date().getTime();
  setInterval(function() {
    // Calculate how much time has passed since 
    // the stopwatch started running
    var currentTime = new Date().getTime() - startTime;
    var seconds = Math.floor(currentTime / 1000) % 60;
    var minutes = Math.floor(currentTime / (60 * 1000)) % 60;
    seconds = (seconds < 10 ? '0' : '') + seconds;
    minutes = (minutes < 10 ? '0' : '') + minutes;
    element.innerHTML = (minutes + ':' + seconds)
  }, 500);
  return element;
}
document.body.appendChild(new StopWatch())

As you can see, the tricks I’ve discussed here have made it possible to write the same meaning in less lines of code. Now imagine this code is part of a huge project. If you can view the entire code, even with comments, without needing to scroll, then it will be easier to understand and modify.

However, I never went short on variable names, nor made this look like minified JS, which will have happen if we start changing element to e, or seconds to s. Using those names for variables will make you stop for a moment and think, In this context, what is e or s?

If you doubt whether you should apply one of these conciseness techniques, ask yourself the following: By making things this way, the next time I come to the code and view it with the eyes of a stranger, will it be easier or harder to understand? Happy coding!

Contributors

Juan Carlos Arias Ambriz

Freelance JavaScript Consultant
Mexico

Juan has over ten years of freelance UX experience. His projects span a wide variety but are all rooted in his commitment to always provide the best experience to the user. Juan has developed applications used by high-profile clients and so has learned to commit to perfection of detail in his work.

Show More

Tip Proposed by Jalik

Author Name: Jalik Author Email: jalik26@gmail.com

Thanks for sharing, just a typo in “the current time is 150,678 ms”, it should be in seconds not milliseconds.

Internationalization API

Presenting data in a human-friendly way is a trait of any good UX, but it has always been a tricky part for developers. This is especially true when an app is available across multiple languages or countries. Each context requires a new set of rules.

For example, presenting relative time formats in English is fairly simple. There is always a maximum of two forms: singular and plural. E.g.: 1 month ago and 2+ months ago.

In Polish, things get more complicated. A noun has multiple forms, depending on the count: 1 miesiąc temu and 2-4 miesiące temu, but 5+ miesięcy temu.

Some companies need to consider such formatting rules for hundreds of languages and contexts (think about the scale of Facebook or Google). Developing that creates a lot of boilerplate code.

Thankfully, modern JavaScript has the global Intl namespace with a set of ECMAScript Internationalization APIs. And that makes things blissfully easy.

Relative Time Format

Let’s tackle the case above. We’ll create a function that returns the difference between two timestamps in a human-friendly format. For this, we’ll leverage Intl.RelativeTimeFormat.

const getRelativeTimeString = (relativeDate, baseDate, locale = 'en') => {
   const rtf = new Intl.RelativeTimeFormat(locale, {
       // displays values like `yesterday` instead of `1 day ago`
       numeric: 'auto',
   })
   // time difference in milliseconds
   const deltaUnix = relativeDate.getTime() - baseDate.getTime()

   // change milliseconds into seconds...
   const deltaSeconds = Math.round(deltaUnix / 1000)
   // ... and check if difference is below 60 seconds (1 minute)...
   if (Math.abs(deltaSeconds) < 60)
       // ... then return formatted date...
       return rtf.format(deltaSeconds, 'seconds')
   // ... else increase the unit

   const deltaMinutes = Math.round(deltaSeconds / 60)
   if (Math.abs(deltaMinutes) < 60)
       return rtf.format(deltaMinutes, 'minutes')

   const deltaHours = Math.round(deltaMinutes / 60)
   if (Math.abs(deltaHours) < 24)
       return rtf.format(deltaHours, 'hours')

   const deltaDays = Math.round(deltaHours / 24)
   if (Math.abs(deltaDays) < 7)
       return rtf.format(deltaDays, 'days')

   const deltaWeeks = Math.round(deltaDays / 7)
   if (Math.abs(deltaWeeks) < 4)
       return rtf.format(deltaWeeks, 'weeks')

   const deltaMonths = Math.round(deltaWeeks / 4)
   if (Math.abs(deltaMonths) < 12)
       return rtf.format(deltaMonths, 'months')

   const deltaYears = Math.round(deltaMonths / 12)
   return rtf.format(deltaYears, 'years')
}

Now, let’s try it out:

const baseDate = new Date(Date.UTC(2019, 05, 13))

getRelativeTimeString(
   new Date(Date.UTC(2019, 04, 13)),
   baseDate,
   'en',
)
// => '1 month ago'

getRelativeTimeString(
   new Date(Date.UTC(2019, 04, 13)),
   baseDate,
   'pl',
)
// => '1 miesiąc temu'

getRelativeTimeString(
   new Date(Date.UTC(2019, 03, 13)),
   baseDate,
   'en',
)
// => '2 months ago'

getRelativeTimeString(
   new Date(Date.UTC(2019, 03, 13)),
   baseDate,
   'pl',
)
// => '2 miesiące temu'

getRelativeTimeString(
   new Date(Date.UTC(2018, 12, 13)),
   baseDate,
   'en',
)
// => '5 months ago'

getRelativeTimeString(
   new Date(Date.UTC(2018, 12, 13)),
   baseDate,
   'pl',
)
// => '5 miesięcy temu'

That was easy! And you do not need an additional set of rules for new languages. Just pass a different locale argument:

getRelativeTimeString(
   new Date(Date.UTC(2019, 03, 13)),
   baseDate,
   'de-AT',
)
// => 'vor 2 Monaten'

What Else Can the Internationalization API Do?

That was just the tip of the iceberg. And the iceberg is full of opportunities!

The Intl namespace is still being developed, and more APIs hopefully will be added in the future. But support is already strong for some parts of Intl both among browsers and in a back-end context with Node.js. For now, here is what is most widely available.

Intl.Collator

Collator allows developers to compare strings in a locale-specific way. After all, alphabetical is a more relative concept than some coders may realize. So Collator can be pretty useful when sorting arrays:

['z', 'ä', 'c'].sort(new Intl.Collator('de').compare)
// -> ['ä', 'c', 'z']

['z', 'ä', 'c'].sort(new Intl.Collator('sv').compare)
// -> ['c', 'z', 'ä']

Intl.DateTimeFormat

DateTimeFormat performs language-sensitive date and time formatting. It comes with a lot of options, but here is a basic example for starters:

const date = new Date(Date.UTC(2019, 05, 13))

new Intl.DateTimeFormat('en-US').format(date)
// -> '6/13/2019'

new Intl.DateTimeFormat('en-GB').format(date)
// -> '13/06/2019'

Intl.ListFormat (Experimental)

ListFormat is still in experimental mode, but it will definitely be my favorite part of Intl. It facilitates displaying arrays as human-friendly lists. For example, transforming ['A', 'B', 'C'] into 'A, B, and C' (or 'A, B and C' by default in en-GB):

const list = ['A', 'B', 'C']

new Intl.ListFormat('en-US', {
   style: 'long',
   type: 'conjunction',
}).format(list)
// -> 'A, B, and C'

new Intl.ListFormat('en-GB', {
   style: 'long',
   type: 'conjunction',
}).format(list)
// -> 'A, B and C'

Intl.NumberFormat

NumberFormat, as its name implies, does number formatting, which is especially handy when dealing with currencies, among other uses.

Dot or comma? Currency symbol before or after the amount? Actually, which currency symbol at all? Now it’s easy:

new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(123.45)
// -> '$123.45'

new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'USD' }).format(123.45)
// -> 'US$123.45'

new Intl.NumberFormat('pl-PL', { style: 'currency', currency: 'USD' }).format(123.45)
// -> '123,45 USD

Intl.PluralRules

PluralRules does plural-sensitive formatting and handles plural language rules. It’s like the above example with RelativeTimeFormat, but much more generic and low-level. It can be useful when defining your own internationalization rules and APIs.


It’s worth checking out the full documentation of the Intl namespace and taking it for a spin. Happy coding!

Contributors

Patryk Pawłowski

Freelance JavaScript Consultant
Poland

Patryk is a seasoned full-stack developer who specializes in all types of modern JavaScript implementations—from architecting the back end and APIs to building pixel-perfect web and mobile apps. Thanks to his experience running his own company and having a background in design, he is a great facilitator between business and product teams. Patryk also enjoys speaking at conferences.

Show More

Template Strings

ES6 adds a new feature called “template strings.” At first glance, they may look similar to string interpolation in other languages such as Ruby and PHP. Yes, they can be used to implement the same behavior:

const name = 'David'
const myStr = `Hello, ${name}!`
console.log(myStr)

This logs Hello, David! to the console. Anything between ${ ... } is interpolated into the string.

But in reality, template strings are a lot more powerful than that. Behold:

const name = 'David'

// Template string functions take the first argument as
// an array of strings which are the pieces of the template string
// and a variadic amount of arguments following that representing
// the inserted tokens at each position
const escape = (parts, ...tokens) => {
    let result = ''

    // iterate the parts array
    for (let i = 0; i < parts.length; i++) {
        result += parts[i]

        // if we haven't reached the end of the parts array... 
        if (i < parts.length - 1) {
            // ... Surround the token with quotes
            result += '"' + tokens[i] + '"'        
        }
    }

    return result
}

const myStrAutoQuoted = escape`Hello, ${name}!`
console.log(myStrAutoQuoted)

This will log Hello, "David"! to the console. The syntax here is important; see how we simply put the name of the template function right before the template string without parentheses. Note how the variable name was automatically quoted! This is just the beginning of how powerful template strings really are. They add another dimension to the possibilities of JavaScript programming.

This feature is used in many new JavaScript libraries, including styled-components, which allows developers to write CSS in JS in a very clean way:

import styled from 'styled-components'
import { render } from 'react-dom'

const RedSquare = styled.div`
    width: ${props => props.size}px;
    height: ${props => props.size}px;
    background-color: red;
`

render(, document.getElementById('container'))

This will render a red square box of size 20 pixels on the screen.

Contributors

David Xu

Freelance JavaScript Consultant
United States

David has taken several mobile apps from an idea to millions of users all over the world, as the Chief Architect of Castle Global, Inc and technical lead in other companies. He has been programming since the age 9 and has won medals in various competitive programming competitions, from the Australian Informatics Olympiad to ACM-ICPC. David has invaluable experience in all areas technical, from architecture and design to engineering and DevOps.

Show More

Submit a tip

Submitted questions and answers are subject to review and editing, and may or may not be selected for posting, at the sole discretion of Toptal, LLC.

* All fields are required
Subscribe

Free E-mail Updates

Get the latest content first

Subscription implies consent to our privacy policy

Toptal Connects the Top 3% of Freelance Talent All Over The World.

Join the Toptal community.