New ECMAScript 23 array features
Published on August 18, 22'
js

Table of contents

Introduction

Periodically, new features are introduced to Javascript - more accurately features are proposed with the ECMAScript standard then integrated into Javascript.

Over the years (just to name a few) we had ES6 which brought in the let and const keyword, ES2020 which had nullish coalescing, optional chaining etc.

The newest proposal, aptly named ES2023, has some new useful features concerning arrays we will get into shortly.

Array by copy

Some array methods mutate an array it was called upon, the most prominent of these being:

  • sort
  • splice
  • reverse

For example:

const x = [3, 2, 1];
const y = x.sort();

console.log(y); // [1, 2, 3] <- Ok
console.log(x); // [1, 2, 3]

Calling the sort method mutated the original array, which might not be neccessary if you planned on keeping the original array the same.

You can get around this if you spread the original array (only if the members of the array are primitive types):

const x = [3, 2, 1];
const y = [...x].sort();

console.log(y); // [1, 2, 3]
console.log(x); // [3, 2, 1]

But why, if you could use a method for this? That's where the new to methods come into play.

In this instance, the toSorted method:

const x = [3, 2, 1];
const y = x.toSorted();

console.log(y); // [1, 2, 3]
console.log(x); // [3, 2, 1]

The proposal is described here, with the other methods being:

  • toReversed
  • toSorted
  • toSpliced
  • with

Array grouping

As the name implies, this method groups its members together with a provided condition akin to the GROUP BY clause one can find in SQL.

Previously, a method that could be used to group elements together could be as follows:

/**
 * Groups values in an array of objects.
 * @param {any[]} array - The array of objects to be grouped by.
 * @param {string} property - The property of the objects to group by.
 * @return {any[]} Array of objects grouped by the provided property.
 */
export function groupBy(array, property) {
  return array.reduce((memo, x) => {
    if (!memo[x[property]]) {
      memo[x[property]] = [];
    }
    memo[x[property]].push(x);
    return memo;
  }, {});
}

Let's say we have an array of employees, defined as such:

const employees = [
  { name: "Alina", company: "Google", id: 1 },
  { name: "Vika", company: "Coca Cola", id: 2 },
  { name: "Alex", company: "Jonson & Jonson", id: 3 },
  { name: "Vlad", company: "Google", id: 4 },
  { name: "Fibi", company: "Coca Cola", id: 5 },
  { name: "Joey", company: "Google", id: 6 }
];

If we wanted to group them together by the company, we'd use the method as follows:

const grouped = groupBy(employees, "company");

With the result being:

{
  Google: [
    {
      name: "Alina",
      id: 1
    },
    {
      name: "Vlad",
      id: 4
    },
    {
      name: "Joey",
      id: 6
    }
  ],
  "Coca Cola": [
    {
      name: "Vika",
      id: 2
    },
    {
      name: "Fibi",
      id: 5
    }
  ],
  "Jonson & Jonson": [
    {
      name: "Alex",
      id: 3
    }
  ]
}

The same can now be achieved with the newly proposed methods, group and groupToMap.

The proposal is described here.

groupToMap

The groupToMap method groups the elements of the calling array using the values returned by a provided testing function. The final returned Map uses the unique values from the test function as keys, which can be used to get the array of elements in each group.

The method is primarily useful when grouping elements that are associated with an object, and in particular when that object might change over time. If the object is invariant, you might instead represent it using a string and group elements with group.

For example, let's say we have an array of game items:

const items = [
  { name: "potion", type: "consumable", price: 25 },
  { name: "sword", type: "weapon", price: 425 },
  { name: "shield", type: "protection", price: 225 },
  { name: "helmet", type: "protection", price: 125 }
];

The code below uses groupToMap() with an arrow function that returns the object keys named unaffordable or affordable, depending on whether the element has a price lesser than 150. The returned result object is a Map so we need to call get() with the key to obtain the array.

const unaffordable = { unaffordable: true };
const affordable = { unaffordable: false };
const result = items.groupToMap(({ price }) =>
  price < 150 ? affordable : unaffordable
);
console.log(result.get(affordable));
// expected output: [{ name: "potion", price: 25 }, { name: "helmet", price: 125 }]

group

The group method groups the elements of the calling array according to the string values returned by a provided testing function. The returned object has separate properties for each group, containing arrays with the elements in the group.

This method should be used when group names can be represented by strings.

Using the method on the array mentioned beforehand, we get a result as follows:

const result = items.group(({ type }) => type);
{
  consumable: [
    {
      name: "potion",
      type: "consumable",
      price: 25
    }
  ],
  weapon: [
    {
      name: "sword",
      type: "weapon",
      price: 425
    }
  ],
  protection: [
    {
      name: "shield",
      type: "protection",
      price: 225
    },
    {
      name: "helmet",
      type: "protection",
      price: 125
    }
  ]
}

Array from async

The array method called from has been standardized and it is very useful for shallow copies of iterables or array like objects.

For example:

const letters = Array.from("hello"); // ["h", "e", "l", "l", "o"]
const squares = Array.from([4, 5, 6], (n) => n ** 2); // [16, 25, 36]

It also provides a mapping callback that can be used on every member of the array.

However, a similar method does not exist for async iterables. Let's say we have a function that generates numbers asynchronously:

async function* asyncGen(n) {
  for (let i = 0; i < n; i++) yield i * 2;
}

Without a dedicated function, one would create an array as follows:

const arr = [];
for await (const v of asyncGen(4)) {
  arr.push(v);
}

The result being [0, 2, 4, 6]. Using the fromAsync method, an equivalent process can be done:

const arr = await Array.fromAsync(asyncGen(4));

The proposal is described here.

Conclusion

Having looked at the newly proposed features, one can see that it is quite handy having them integrated into the default language features as you do not need to write your own implementations.

©   Matija Novosel 2024