Skip to content
    All posts
    DIFFICULTY RATING:
    Easy
    LANGUAGE:
    English

    New(ish) Javascript features that could prove useful to you

    As we become more and more familiar with the language we use on a daily basis we tend to focus on and reiterate the patterns that we've always found working, often discarding alternatives.

    Neosperience Tech Blog 29

    At the same time, the language keeps evolving, adding new tools here and there. While maybe not as conspicuous as what was introduced with ES2015 (ES6), some of these recent additions are still worth taking a look. 

    This article delves into some of these recent features, highlighting how they can be used within possible contexts of application.

    Immutable array methods for sort, splice, reverse

    An annoying aspect of these otherwise quite useful methods has always been that they mutate the original array.

    Suppose we wanted to get the largest number in an array. We could (not that it is the best approach) sort the array from smallest to largest and then get the last element:

    const arr = [2,6,1,6,7,4,3]
    const largest = arr.sort().at(-1) // 7

    The problem with this approach is that sort mutates the original array. Which is to say that if we later access arr we'll find that it has been changed from [2,6,1,6,7,4,3] to  [1,2,3,4,6,6,7].

    Why this is bad would be a discussion worth by itself of a whole article, hence I refer you to one of the many on this topic, and to this for the same in the context of React.

    A workaround could be to create a new array via spread operator just before sorting it, like with [...arr].sort().at(-1). In that case we would be mutating a throw-away array that doesn't persist in memory. We get the latest element and keep the original array as it was at the time of creation.

    This works although creating unnecessary arrays is obviously not ideal. With ECMAScript 2023, the 14th edition, new array methods have been introduced that perform operations returning new arrays and leaving the original ones unaltered: toSorted, toSpliced, toReversed

    Hence the same can be written as:

    arr.toSorted().at(-1)

    Additionally, in the same edition also findLast and findLastIndex have been included.

    Until then if we wanted to find the last element that satisfies a certain condition we would have to either use a for loop with some logic or to find the first element on an array that has been reversed.

    Say we wanted to find the last element that is a number within a mixed array ['a', 2, {pippo: 42}, 42, false]

    [...arr].reverse().find((x) => typeof x === 'number') // 42

    Or better:

    arr.toReversed().find((x) => typeof x === 'number') // 42

    Even better, using findLast:

    arr.findLast((x) => typeof x === 'number') // 42

    Grouping arrays by criteria

    Suppose we have an array of objects:

    const arr = [
      {name:'John', age: 23, team: 'A'},
      {name:'Francis', age: 54, team: 'C'},
      {name:'Anne', age: 34, team: 'B'},
      {name:'Mike', age: 42, team: 'A'},
    ]

    And we wanted to divide them into arrays that represent teams, we would normally need to write some custom code. Being that we want to pass from an array to an object, a good candidate for this would be reduce:

    arr.reduce((acc, item) => {
      acc[item.team] = [...acc[item.team] ?? [], item];
      return acc;
    }, {});

    The same can be achieved more conveniently with Object.groupBy, introduced with ECMAScript 2024, the 15th edition:

    Object.groupBy(arr, (obj) => obj.team)

    Which would return:

    {
      A: [
        { name: 'John', age: 23, team: 'A' },
        { name: 'Mike', age: 42, team: 'A' },
      ],
      C: [{ name: 'Francis', age: 54, team: 'C' }],
      B: [{ name: 'Anne', age: 34, team: 'B' }],
    };

    Whichever string is returned by the callback function is used as property in the resulting object.

    For instance,  if we wanted to get arrays that represent people over or under 40 years of age:

    Object.groupBy(arr, ({age}) => age < 40 ? 'Under 40' : 'Over 40')

    Would give us:

    {
        'Under 40': [
          { name: 'John', age: 23, team: 'A' },
          { name: 'Anne', age: 34, team: 'B' },
        ],
        'Over 40': [
          { name: 'Francis', age: 54, team: 'C' },
          { name: 'Mike', age: 42, team: 'A' },
        ],
      };

    Now, if you are asking yourself: “Cool, but why on Earth did they make this a static method on the Object class rather than a public method on the Array instance??” then I am with you.

    Exactly as we do arr.filter(...), it would feel way more natural to just do arr.groupBy(...).

    I think the reason is that Object is to be intended as what we want the result of the method to be. We want it to be an object. 

    If we instead wanted the result to be a Map we would use Map.groupBy (which also exists).

    Separating Promise result and resolver

    Another API introduced in the same version is Promise.withResolvers. It allows us to separate the promise (what we normally await) and the resolvers functions, that are invoked when a certain condition is met. 

    We can await the promise in one place and delegate the resolving logic to a different piece of code.

    We could have a function that accepts resolvers:

    function asyncOperation(resolve, reject){
      someFunction()
        .then(resolve)
        .catch(reject)
    }

    And pass to this function just the resolvers, which we will use in our business logic:

    const {promise, resolve, reject} = Promise.withResolvers()
    
    promise.then(handleSuccess).catch(handleError)
    
    if(someCondition){
      asyncOperation(resolve,reject) 
    } else {
      // no need for further checks, resolve immediately
      resolve('all good')
    }

    Before the introduction of this API, the same could have been achieved with a bit of custom code:

    function getPromiseResolvers(){
    
      const out = {
        promise: null,
        resolve: null,
        reject: null
      }
    
      out.promise = new Promise((resolve, reject)=>{
        out.resolve = resolve
        out.reject = reject
      })
    
      return out
    }

    New Set methods

    Sets are collections of values, being those primitives or object, guaranteed to be unique.

    They were introduced, among many other features, in ES2015.

    One of the most frequent uses in everyday code has always been to get a unique list from an array.

    const arr = [2,2,4,5,5,65,44,5]
    const uniqueArr = [...new Set(arr)] // [2,4,5,65,44]

    Cool, but a bit of a limited use for a whole type.

    That said, Set has recently been powered with a whole list of composition methods that allow for the typical mathematical operations performed over sets.

    The applications can be many. Here in order to illustrate the methods I will be using the simple case of a user input and a limited set of characters that the user should be allowed to input:

    const allowedChars = new Set(['a','b','c'])
    const userInputChars = new Set(['a','b','x'])

    We can easily get information like:

    Has the user entered only allowed characters?

    userInputChars.isSubsetOf(allowedChars) // false
    or equivalently:
     
    allowedChars.isSupersetOf(userInputChars) // false

    Has the user entered only invalid characters?

     userInputChars.isDisjointFrom(allowedChars) // false

    Which invalid characters has the user entered?

    userInputChars.difference(allowedChars) // ['x']

    Which valid characters has the user entered?

     userInputChars.intersection(allowedChars) // ['a','b']

    Other methods for which it would feel a bit of a stretch to find a valid application on this context are:

    union (all the elements from both sets)

     userInputChars.union(allowedChars) // ['a','b','c','x']

    symmetricDifference (all the elements that are not on both sets)

    userInputChars.symmetricDifference(allowedChars) // ['c','x']

    These new methods achieved Baseline status in June 2024, indicating broad browser support. At the time of writing they haven't been formally incorporated into an official ECMAScript version.

    ECMAScript versions typically undergo a standardization process before official release. The new Set methods are likely candidates for inclusion in a future ECMAScript version, potentially ES2025 or later.

    Conclusion

    This is a selection of the new(ish) features that I considered to be of interest. 

    If you were already familiar with all of them, then kudos to you! 

    Otherwise, it couldn't hurt to keep an eye out for situations where any of these may prove useful. You could find that the problem you are facing can be tackled with less code, in a more direct and more legible way that you were used to. 

    Thanks for reading. Happy coding!

    References

    ES2015 (ES6) features

    Mutations can be scary

    Should you mutate object? (React)

    MDN toSorted

    MDN toSpliced

    MDN toReversed

    MDN findLast

    MDN findLastIndex

    MDN Object.groupBy

    MDN Map.groupBy

    MDN Promise.withResolvers

    MDN Set

    MDN Set composition methods

     

    NEWSLETTER

    Never miss the latest tech news