In the previous post, I explained to you arrow functions, classes, and extended Parameters handling.
In this last post of the series we are going to explore:
- Iterators and for…of operator
You are probably not going to create your own symbols on a daily basis. However, you will probably use existing well-known symbols often.
Symbol is a unique globally unguessable value within the context of your program.
So, to create a new value of type symbol, you need to call the Symbol function. This function optionally takes a string as a parameter. This string is a description of that symbol.
The description is only useful from a debugging perspective. However it has not use at the programmatic level.
Symbols are used to define properties inside an object
Symbol properties don’t appear in Object.keys() or Object.getOwnPropertyNames(). If you want to obtain the symbol properties you have to use the Object.getOwnPropertySymbols()
Well Known Symbols
Here are some of them:
- Symbol.iterator: A method returning the default iterator for an object. Used by for…of
- Symbol.toStringTag: A string value used for the default description of an object
- Symbol.toPrimitive: A method converting an object to a primitive value.
Iterators and for…of operator
Let’s see an example that will help us to understand what iterators are and which collections we can iterate.
So instead of using the traditional for loop, we are going to use the for…of statement to loop an array.
You may be thinking, how the for…of loop is different from the for…in loop? well , while for…in iterates over enumerable, non-symbol properties, the for…of loop iterates over data that a certain object defines to be iterable.
Iterable objects are just objects that have defined an iterator function that let the for…of loop knows how to iterate. This allows you to customize the iteration behavior of an object or create a custom object to be iterated by using the for…of loop.
The iterator method will be a property named Symbol.iterator inside your object.
The iterator method needs to have a next() method that returns an object with a property value and a property done.
The value will contain a value to use as a variable of the for…of loop, and the done will be a boolean value that is going to indicate if there are more objects to iterate or not.
Let’s see how it works. When the done property of the object returned by the next() method is true, the for…of loop understands there are no more elements to iterate.
The following diagram will help you understand how iterators work:
Iterators are also used in the spread Operator.
Behind the scene, what the spread operator does is to pull out each value from a and insert it into the array.
Create a Custom Iterator
What if I have a value, an object, or something that I have created and would like it to be consumable by any of those various iterable consumptions like the spread operator or the for…of loop? In that case, we need to create an iterator for that object.
Let’s see how it works with an example:
Imagine you have this object obj and you need that when the for …of loop is called, it only iterates over position 2 to 5 of values array.
Now the object contains an iterator under the property Symbol.iterator. This function returns a variable iterator that returns an object with the value and a true or false for the done property.
Here is another example, let’s image you have a maximum of 400 USD to make a raffle.
We have a list of 7 participants and 4 possible prizes. We want to have as many winners as the total amount allows.
There is a getRandomIndex(n) function that given a maximum number it returns a number between 1 and that number.
The for… of loop will call the next() method of the iterator. The iterator will start with a total of 0, each time that the next method of the iterator is called it will use a random name and a random award
- if the total + the award is less than max, then it will return the winner with the award and done: false.
- else if max — total is greater than 0, then it will re-calculate the award and return the winner and the new award and done: false.
- else it will return done: true to indicate that the object has no more items to iterate.
This will produce different results each time we call it. For example, if the first time the winner wins 400 USD, there will not be more results, but if the user wins 300 USD, there will be another winner who will win 100 USD.
To define a generator, a “*” should be included after the word function. It is important to point out, that calling the generator doesn’t execute its code; instead, it returns an iterator.
Many people refer to generators as pausable functions. To pause the function a return a value you have to use the word yield. When yield is called, the function is paused but it remembers the state, so the next time you call it, it will have the same values it had when you yielded.
So do you remember the example of iterators where we have an object obj that contains values and we want that when the for…of loop is called, it only iterates over position 2 to 5 of values array? Lets rewrite it using generators and you will see that it is shorter and easier to read.
To understand this, let’s see an example:
Here we have a function doAsync and the purpose of this function is to return a promise.
So we let p = new Promise and the constructor takes a function.
This function is passed two arguments, resolve and reject. If we want the promise to resolve and be fulfilled, we call resolve as a function, and if some error occurred, we call reject as a function.
So the first thing that is done is to log out “in promise code and the dateTime”.
Then, after 2 seconds, the resolve is called.
But this is not exactly the way we work with promises. The reason why we use them is to wait until they are fulfilled or rejected to continue executing our code. So let’s see a more realistic example:
So here we have the function doAsync that returns a new promise. The function takes one parameter fullfillThePromise if this is true, the promise is resolved; else, it is rejected.
When we call the doAsync function we use the then method to indicate that it should wait for the doAsync function to finish to continue.
The then method takes 2 functions as parameters. The first one is the callback function that is going to be executed when the promise inside the doAsync function is resolved and the second one is the one that is going to be executed if the promise is rejected.
As we can see the doAsync starts executing and log “in the promise codeTue…”, then it waits 2 seconds and as fullfillThePromise was true it logged “resolving the promise Tue Oct…” and called the resolve() function. after that the first function of the .then method is called and “the promise has been resolved” is logged.
Now, let’s see how it works when we reject the promise:
It is the same as the previous example the only difference is that fullfillThePromise is false, so the reject was called instead. As a result, the second function of the .then method is executed.
A value can be returned in the resolve or reject callback.
Let’s see an example:
Here we have the same doAsync function as before, but the difference is that when we called it we have 2 .then methods, one in line# 18 and another one in line# 24.
The second .then method will be executed after the first one is completed.
So, in this example, as fullfillThePromise is false the promise will be rejected.
After that, the .then method in line #18 will be executed. The doAsync promise was rejected so, it will execute line #22 and log “the promise has been rejected with 404” in the console, after that, it will return the string “Process could not continue because doASync returned 404”. As it is returning a string it will be wrapped in a resolved promise. This will cause the second .then in line #24 to be executed so it will log in the console “Process could not continue because doASync returned 404”.
The reject method of the .then in line #24 will only be executed if there is an exception inside the .then in line #18.
Instead of returning a string, we normally return a new promise. Like this:
As you can see in the example above, there are two .then methods one in line # 26, which be executed when the doASync promise is fulfilled, and another one in line #32 that will be executed after the rollBackChanges promise is resolved or the string “yeyyy the promise has been resolved” is returned.
Sometimes we need more than one promise to finish before we continue executing the code. In that case, we use the Promise.all method, that receives an array of promises as a parameter.
It is important to mention that Promise.all works as an and, so if one of the promises is rejected, then the reject callback of the .then method will be executed.
In case we have more than one promise and we want to continue executing the code after one of them is fulfilled or rejected we can use Promise.race. The first promise that is fulfilled or rejected is the one treated in the .then method.
Along these posts, I have covered most of the new features released in ECMAScript 6 and provided some examples of them all. However, there are some features I didn’t include such us internationalization & localization, proxying and reflection, etc. If you are interested in those topics you can find more information in the sources linked at the end of this post.
Thanks for reading!
Additional Posts in the Same Series
Would you like to know more? Do you need our help? Contact Us!