loading...
Cover image for Let's build a garage!

Let's build a garage!

aminnairi profile image Amin ・4 min read

Behind this non-techy title hides a little trick in JavaScript that will make you love loops.

Let's say you have a garage. You wanted to manage your garage in JavaScript obviously because JavaScript is the best language for managing vehicles in a garage. Not convinced? Well, famous people are.

Not only JavaScript is the best language in the world, but it has allowed me to increase the management capability of my motorcycle garage by +99999%. ­— Keanu Reeves, 2019.

Okay, now that you are convinced, let's get started with a little bit of code.

The initial setup

We will write a simple, yet powerful garage class that will hold all of our vehicles.

"use strict";

class Garage {
  constructor() {
    this.vehicles = [];
  }

  add(vehicle) {
    this.vehicles.push(vehicle);
  }
}

And then, we will need to instanciate a new garage to store our vehicles.

const garage = new Garage();

Now, we can store our vehicles inside of our garage.

garage.add("Triumph Street Triple");
garage.add("Mazda 2");
garage.add("Nissan X-Trail");

And what about looping over them to list all of our vehicles?

for (const vehicle of garage.vehicles) {
  console.log(vehicle);
}

We can already see the result of our script by using Node.js.

$ node main.js
Triumph Street Triple
Mazda 2
Nissan X-Trail

Great! or is it?

To complexity and beyond!

Most of the time, our classes will be more complex that this simple example. Let's say that our garage is now making a clear distinction between motorcycles and cars. While still holding them all together. Gotta listen to the orders of the garage holder, right?

  constructor() {
-   this.vehicles = [];
+   this.cars = [];
+   this.motorcycles = [];
  }

We may also need to change a bit our add method to reflect the distinction as well.

- add(vehicle) {
-   this.vehicles.push(vehicle);
- }
+ addMotorcycle(motorcycle) {
+   this.motorcycles.push(motorcycle);
+ }
+
+ addCar(car) {
+   this.cars.push(car);
+ }

As well as the way we add vehicles into the garage.

- garage.add("Triumph Street Triple");
+ garage.addMotorcycle("Triumph Street Triple");
- garage.add("Mazda 2");
+ garage.addCar("Mazda 2");
- garage.add("Nissan X-Trail");
+ garage.addCar("Nissan X-Trail");

We can now run our script. This should work as intended, right?

$ node main.js
for (const vehicle of garage.vehicles) {
                             ^

TypeError: garage.vehicles is not iterable

What's wrong?

You see, we now have removed the garage.vehicles property and instead we have two properties that holds our vehicles. We could have made two loops and loop over these two properties. We could even merge the two arrays into one and loop over it. Why not, let's do it!

- for (const vehicle of garage.vehicles) {
+ for (const vehicle of [...garage.motorcycles, ...garage.cars]) {
    console.log(vehicle);
  }

Let's test this out:

$ node main.js
Triumph Street Triple
Mazda 2
Nissan X-Trail

Yay! Working as intended. But it made our syntax less readable than before, less natural. Now imagine our garage increased in popularity and people from around the country want repairs for their bicycle, bus, trucks, ... Will you keep doing that? Of course yes! I mean no!

Do you have a moment to talk about our lord and savior, Iterator Protocol?

There is this strange world of iterator hidden under the JavaScript language. The saying says that once you go in there, you never really come back as one. You are part of something greater. You are part of the JavaScript language now. You feel whole, but you also feel connected to the inner system calls made by the JavaScript engine. But before feeling this power, we will need to refactor our code a bit.

  class Garage {
+   *[Symbol.iterator]() {
+     for (const vehicle of [...garage.motorcycles, ...garage.cars]) {
+       yield vehicle;
+     }
+   }
  }

Okay! But wasn't what we did earlier? With a bit of new syntax? Yes of course, but we now are able to use a syntax that is more readable. Maybe not natural because we do not iterate over objects often, but it now allows us to iterate the object with the for...of loop and the triple-dot-syntax.

The star tells the engine that our function is now a generator function. A special kind of function that will help us return something that is compliant with the iterator protocol. The symbol will allow the iteration of our instances with the for loop (and the triple-dot-syntax) as well as use our instance in all methods that take an iterable as their argument. For instance, we would now be able to do something like:

Array.from(garage).map(vehicle => console.log(vehicle));

And that would work fine!

Usage

Now that everything is setup, we can go back to our first initial definition of the for loop.

- for (const vehicle of [...garage.motorcycles, ...garage.cars]) {
+ for (const vehicle of garage) {
    console.log(vehicle);
  }

Will it work? (spoiler: it will)

$ node main.js
Triumph Street Triple
Mazda 2
Nissan X-Trail

But wait, there is more!

Now that we are using this new protocol and the iterator symbol, we can do cool things like looping over it without for loop:

+ [...garage].map(vehicle => console.log(vehicle));
- for (const vehicle of garage) {
-   console.log(vehicle);
- }

This can be great to filter out vehicles by names for instance:

- [...garage].map(vehicle => console.log(vehicle));
+ [...garage]
+   .filter(vehicle => vehicle.toLowerCase().includes("triumph"))
+   .map(vehicle => console.log(vehicle));

Running this would gives us only Triumph motorcycles (though, we only have one, these bikes are pretty expensive you know!).

$ node main.js
Triumph Street Triple

The end

That is all there is for now folks! If you are interested about this subject, you can check out the documentation about the Symbol.iterator and the Iterator Protocol as well.

You can play with that example online here.

Will you use that feature? Do you think it helps or adds more complexity to your apps? Let me know in the comment section!

Posted on Aug 16 '19 by:

Discussion

markdown guide