New ECMAScript 23 array features
Tags: javascript
Published: 2022-09-15 09:47:07

## 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:

```javascript
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):

```javascript
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:

```javascript
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](https://github.com/tc39/proposal-change-array-by-copy), 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:

```javascript
/**
 * 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:

```javascript
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:

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

With the result being:

```javascript
{
  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](https://github.com/tc39/proposal-array-grouping).

## 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:

```javascript
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.

```javascript
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:

```javascript
const result = items.group(({ type }) => type);
```

```javascript
{
  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:

```javascript
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:

```javascript
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:

```javascript
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:

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

The proposal is described [here](https://github.com/tc39/proposal-array-from-async).

## 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.