JavaScript Symbols, Generators and Streams

Graffitis on a train — Picture under the Creative Commons license (source: https://flic.kr/p/4ZKKgo)

Introduction

The Symbol and Generator were added on ES2015 and they use is pretty blurry for some developers. So, in this article, I’ll try to simplify and explain it, then show some use cases.

My objective isn’t to show everything related to them, but touch in some interesting aspects of both and the correlation between they usages, trying to being simple and concise for better understanding. Enjoy it!

What is Symbol?

There’s some curious specifications about it:

  • They’re primitive data type;
  • They’re factory functions, not constructors (you can’t use the new keyword);
  • They are unique: Symbol(“foo”) == Symbol(“foo”) returns false;
  • Can be used as non-enumerable object property keys.

The two last parts are pretty important, so let’s go deeper on it.

Staying anonymous

So if you want to protect some properties/methods of an Object, you can basically do this using the Symbol instances in object literals:

And in classes/constructors you can do like this:

Note: As JS objects aren’t restricted to “strings” as property keys, we can use the Computed property names to use a Symbol instance as a property of literal and constructed object instances.

Pretty easy right? And when you try to enumerate the properties of this instance (like using Object.keys), you’re going to have only:

[ 'hello' ]

And you may think, “this is great for creating object where I can iterate over then!”. Actually the Symbols are more for accessing protection. For iteration over object we have a better solution (and we’re still going to use Symbols for that):

Well-known Symbols

There’s a special type of Symbol, which we can use to create a default iterator method for our objects, just using it as the property key.

Note from the writer:
This really reminds me the Python Data Model, where the language itself define some method signatures (called there as the double-underscore methods, a.k.a. “dunder methods”) based on the “Hollywood Principle” (which I described a little in my previous post). With this, the language knows what to call in the instance when you want to iterate over, print it, overload operator behaviours, etc.

Iterables and Iterators protocols

Let’s do an example. Let’s create a Train class, which have as properties wagons and metersSize and a method beep. But we want the train to be iterable, naturally over the wagons. So, it will be like this:

We can use the well-know Symbol, Symbol.iterator to create the protocoled method called by the language when you try to iterate over the object using the for … of loop or the spread operator. This method should return an iterable object, protocoled on the format:

{ next():{ value:any, done:boolean } }

The next is called at each iteration, using the result value as the current iteration value, and stopping when receiving a done. But there a smarter way to create iterables…

Generators

Generator functions are special type of functions which are able to freeze and save its context after each call using the keyword yield. To declare this type of functions we need to use the following notation:

function* myGenerator() { ... }

This way, when invoked, this function will return a generator object, which is a iterable too (have the next() method returning the {value, done} object).

To simplify the above explanation, in the example below, we see that we’re able to use the iterator method as a generator function:

For each call of the function, it will execute until find the next yield, returning the value and freezing the context. The next call is executed right after the yield (in this case, going to the evaluation step inside the for again). When reaching any return (or the end of the function), it will return the done property as true.

But as we can see in the docs, is possible to return an iterable using the yield* command:

Dealing with Asynchronous data

Ok, we’ve created a nice Train class and everything, but we know in the real world we need to deal with asynchronous data. For that, let’s create a use scenario:

Let’s suppose for iterate over a train we need to fill the wagons first, and as we have only one team of workers to do this job, we need to fill one wagon before going to the next. Let’s create a factory function capable of doing this job:

The payment train have now two wagons of gold (waiting to be filled) and one of people (don’t need the workers to be filled).

So, how we’re going to iterate on it respecting the filling times and the sequence?

Creating a stream

Let’s create a function capable of read a generator which can return Promises in the values and then, being capable of resolve them for us:

It’s a simple, but a flexible function. It can receive non Promises too, and it waits for the subscriber sub to resolve any asynchronous data before going to the next iteration. The subscriber can also cancel the iteration throwing an exception, causing the chain of then to stop of being called and triggering the catch. To check if it ended, just pass the complete function or use the Promise returned by the stream.

So let’s test our paymentTrain and look at the results:

“Waaaait, I’ve saw something similar to this before!”

If you’re thinking in async/await, you’re right! The yield in this context have a pretty similar behaviour of the await, waiting the resolution of the wagon to continue the execution. If you look in this Babel Plugin, it uses this behaviour to simulate the async/await feature.

Anyway, going back to the example, let’s look the output:

Time to check the train!
---
engine <-- this appears instantly
---
coal <-- this too
---
gold <-- this appears after 2 seconds
---
gold <-- this appears after 3 seconds
---
people <-- this appears instantly
---
Ready to go!

The nice thing is that wasn’t so complex to create this stream, and the iterate function can be used for other situations too. Of course you can use async/await instead, and if you really want to apply the concepts of “streams” on production systems, please check a more powerful solution like RxJS.

You can check the final result here on the CodePen.


“I’m goin’ off the rails on a crazy train”

If you liked this article, enter in the supporters train and please give it some claps 👏!

Don’t forget to leave some comments and critics too, feedbacks are always welcome and helps me to improve the content for you ❤️.