Modern Javascript Recap - Part II

Joris Verbogt
Joris Verbogt
Feb 11 2022
Posted in Engineering & Technology

Using Loops and Iterables

Modern Javascript Recap - Part II

Since the introduction of modern JavaScript with ES5 and later ES6, common language patterns and structures were introduced that lifted the limited expressive power of the original JavaScript language to the level of more robust programming languages.

In the first part of this blog post, I touched on Map and Set, in this second part, I will take a look at what is possible in modern JavaScript when it comes to iterating over collections.

Array Loops

A very powerful and more readable loop structure was introduced, the for..of loop:

let values = [ 1, 2, 3, 4, 5]

for (const value of values) {
  console.log(value) // 1 2 3 4 5
}

One of the best parts, is that it works naturally with code that uses async/await syntax:

for (const value of values) {
  await saveToDatastore(value)
}

Which makes for very concise and readable code.

Iterables

Now, with arrays we already have a datatype that allows a collection of data to loop over, but a lot of times, data is of unknown size, even indefinite. This is where the Iterable protocol comes in very handy. An Iterable can be any object that implements the @@iterator method (accessible via [Symbol.iterator] property). Calling that method should return an Iterator, which is simply an object that has one method: next(). Each call to next() will return the next value, plus a boolean indicating if this was the last value.

String, Array and Set are all examples of an Iterable, but imagine a class that will list the values at the even indexes of an array:

class SimpleIterator {
  constructor(data) {
    this.data = data;
  }

  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        if (index < this.data.length) {
          const value = this.data[index]
          index += 2
          return {value: value, done: false}
        } else {
          return {done: true}
        }
      }
    }
  }
}

const simple = new SimpleIterator([ 1, 2, 3, 4, 5 ]);

for (const val of simple) {
  console.log(val); // '1' '3' '5'
}

Await Loops

We can go one step further, by introducing an AsyncIterator, which is basically an iterator that returns a Promise that resolves with {value, done}, just like its synchronous counterpart. Imagine we have a datastore that allows us to query blog posts and returns us a cursor (or stream of blog posts) of unknown length.

If that datastore implements an AsyncIterator, we can combine our loops and async/await to truly create an asynchronous loop:

const cursor = blogposts.query({ tag: 'engineering'})

for await (const post of cursor) {
  console.log(post.title)
}

Destructuring and Spread

When working with arrays of data, modern JavaScript gives you two very useful language constructs that allow you to write more concise code: Destructuring and Spread Syntax.

Assigning values to multiple variables can quickly lead to many lines of code, even though the assignment consists of one logical block.

let values = [ 1, 2, 3, 4 ]
let a = values[0]
let b = values[1]
let c = values[2]
let d = values[3]

Wouldn't it be nice if this could be done in one go? With Array Destructuring, you can!

let a, b
[ a, b ] = [ 1, 2 ]

Extra values are ignored, missing values are undefined:

let values = [ 1, 2, 3 ]
let a, b, c, d
[ a, b ] = values
console.log(a) // 1
console.log(b) // 2

[ a, b, c, d ] = values
console.log(c) // 3
console.log(d) // undefined

Array Destructuring is especially useful if you want to swap values without the need for an intermediate variable:

let a = 1
let b = 2
[ a, b ] = [ b, a ]
console.log(a) // 2
console.log(b) // 1

But what if the length of your data collection is unknown and you still want to use all values?

The Spread syntax is a powerful way to use arrays of values and assign them to variables:

let values = [ 1, 1, 2, 3, 5, 8, 13 ]
let first, second, rest

[ first, second, ...rest ] = values
console.log(second) // 1
console.log(first) // 1
console.log(rest) // [ 2, 3, 5, 8, 13 ]

This can be especially useful if you want to pass an array of values as separate parameters to a function call:

let values = [ 1, 2 ]
function add(first, second) {
  return first + second
}
console.log(add(...values)) // 3

It also works with any Iterable, so for example with Set:

let values = new Set()
values.add(1)
values.add(2)

console.log(add(values)) // 3

It's important to not confuse Spread with the Rest Parameter syntax for functions, which is kind of the opposite of the spread, but is still handy to give you an array of input values of variable length:

function remainder(first, second, ...params) {
  return params
}
console.log(remainder(1, 2, 3, 4, 5)) // [ 3, 4, 5 ]

Conclusion

Since the introduction and wide-spread adoption of async/await and iterables, there is nothing stopping you from using these powerful language features, especially when used in NodeJS applications.

As always, we hope you liked this article and if you have anything to add, maybe you are suited for a Developer position in Notificare. We are currently looking for a Core API Developer, check out the job description. If modern Javascript is your thing, don't hesitate to apply!

Keep up-to-date with the latest news