JavaScript Symbols, Iterators, Generators, Async/Await, and Async Iterators
Some JavaScript (ECMAScript) features are easier to understand than others. Generators look strange — like Pointers in C/C ++. Symbols looks like primitive values and objects.
These functions are all interrelated and build on each other. So you can’t understand one thing without understanding another.
Therefore, in this article, symbols, global-symbols, iterators, iterables, generators, async/await and Async iterators will be introduced. Will first explain the “why” and show how they work with some useful examples.
This is a relatively high-level topic, but it’s not complicated. This article should give you a good grasp of all these concepts.
Ok, let’s get started
Symbols
In ES2015, a new (sixth) data type, Symbol, was created.
Why is that?
The three main reasons are:
Reason 1 – Add new core functionality with backward compatibility
JavaScript developers and the ECMAScript Committee (TC39) needed a way to add new object attributes without breaking existing methods like for… In loops or JavaScript methods like object.keys.
For example, if an Object, var myObject = {firstName:’raja’, lastName:’rao’} runs object.keys (myObject) it will return [firstName, lastName].
Now, if we add another property, newProperty for myObject, if we run object.keys (myObject) it should still return the old value (that is, somehow make it ignore the newProperty), And display only [firstName, lastName] instead of [firstName, lastName, newProperty]. How do you do that?
We couldn’t really do this before, so we created a new data type called Symbols.
If newProperty is added as symbol, then Object.keys(myObject) ignores it (because it does not recognize it) and still returns [firstName, lastName].
Reason 2 – Avoid name conflicts
They also want to keep these attributes unique. This way, you can continue to add new attributes to the global (and you can add object attributes) without worrying about name conflicts.
For example, if you have a custom object, add the custom toUpperCase function to the object to global Array.prototype.
Now, suppose to load another library (or libraries) released the ES2019 with a custom function and it has different Array. The prototype. The toUpperCase. Custom functions may then be broken by name conflicts.
So how do you resolve this name conflict that you may not know about? This is where Symbols comes in. They create unique values internally, allowing you to create added attributes without worrying about name conflicts.
Reason 3 – Allows hooks to call core methods through “Well known” Symbols
Suppose you need some core methods, such as String.prototype.search to call custom functions. That is, ‘someString’.search(myObject); We should call myObject’s search function and pass ‘someString’ as an argument. What should we do?
This is why ES2015 proposed a series of global Symbols called “well known” Symbols. As long as your object has one of these Symbols as a property, you can reposition the core function to call your function.
We’ll leave that out for now and cover all the details later in this article. But first, let’s see how Symbols actually works.
Create Symbols
The Symbol Symbol can be created by calling the Symbol global function/object. This function returns the value symbol of the data type.
Pay attention to
For example: Symbols have methods similar to objects, but unlike objects, they are immutable and unique.
The “new” keyword cannot create Symbols
Since Symbols are not objects and the new keyword should return Object, we cannot use new to return the Symbols data type.
Var mySymbol = new Symbol (); // Throw an error
Symbols has “description”
Symbols can have a description – it is only used for recording purposes.
// The mySymbol variable now contains a unique "Symbols" value // it is described as "some text" const mySymbol = Symbol ('some text');Copy the code
The Symbols is unique
Const mySymbol1 = Symbols ('some text'); Const mySymbol2 = Symbols ('some text'); mySymbol1 == mySymbol2 //false
Copy the code
If we use the “symbol. for” method, the Symbols act like a singleton
Instead of creating a Symbol through Symbol(), create a Symbol through symbol.for (
). This requires a “key” (string) to create a Symbol. If the Symbol for a key already exists, it only returns the old Symbol. So, if we use the symbol.for method, it acts like a singleton.
Var mySymbol1 = symbol.for ('some key'); // create a new symbol var mySymbol2 = symbol.for ('some key'); // ** returns the same symbol mySymbol1 == mySymbol2 //true
Copy the code
The real reason for using.for is to create a symbol in one place and access the same Symbols from somewhere else.
Note: symbol. for overwrites the previous value if the key is the same, which makes the Symbol non-unique, so avoid this as much as possible.
The key and description of Symbols
Just to make things clear, Symbol is unique if you don’t use symbol. for. However, if Symbol. For is used and the key is not unique, the Symbol returned is not unique either.
Symbols can be an object property key
This is a very unique thing for Symbols ———— and the most confusing. Although they look like an object, they are primitive. We can add Symbol to the object as a property key, just like String.
In fact, this is one of the main ways to use Symbols as object properties.
Pay attention to
The [] operator and the. Operator
The. Operator cannot be used because. The [] operator applies only to string attributes, so use the [] operator instead.
3 main reasons to use Symbol – Review
Let’s review three main reasons to understand how Symbol works.
Reason 1-symbols is not visible to loops and other methods
The following example uses a for-in loop to iterate over an object obj, but it doesn’t know (or ignores) prop3 and prop4 because they are symbols.
Object.keys
Object.getOwnPropertyNames
The reason 2-symbols is unique
Imagine calling array.prototype. includes on a global Array object. It will conflict with the JavaScript (ES2018) default method includes. How do I add it without conflict?
First, create a variable includes with an appropriate name and assign a symbol to it. This variable (now symbol) is then added to the global Array using parenthesis notation. Assign whatever functionality you want.
Finally, the function is called using parenthesis notation. Note, however, that the actual symbol, such as arr[includes](), must be passed in parentheses instead of a string.
Reason 3- Well known Symbols (i.e. “global” Symbols)
By default, JavaScript automatically creates a bunch of Symbols variables and assigns them to the global Symbol object (using the same Symbol() to create the Symbols).
ECMAScript 2015, these Symbols are added to core objects such as sets of numbers and String core methods such as String.prototype.search and string.prototype. replace.
Some examples of these symbols are: symbol.match, symbol.replace, symbol.search, symbol.iterator, and symbol.split.
Since these global Symbols are global and public, we can use core methods to call custom functions instead of internal functions.
An example:Symbol.search
For example, the string.prototype. search public method of String searches for regExp or String and returns the index (if found).
In ES2015, it first checks to see if the symbol.search method is implemented in the query regExp (the regExp object). If it does, it calls the function and delegates the work to it. A core object like RegExp implements symbolsymbol.search, which actually does the work.
The inner workings of symbol.search
- parsing
'rajarao. Search (" rao ");
- will
"Rajarao"
Convert to StringNew String (" rajarao ")
- will
"Rao"
Convert to a RegExp objectNew Regexp (" rao ")
- Calling a string object
"Rajarao"
The method ofsearch
To pass'rao'
Objects are parameters. search
Method calls the internal method of the rao objectSymbol.search
(returns the search delegate to the rao object) and passes"Rajarao"
. Like this:"rao"[Symbol.search]("rajarao")
"rao"[Symbol.search]("rajarao")
Returns the index result 4 passed tosearch
Delta function, and finally,search
Return 4 to our code.
The following pseudocode snippet shows how the code works inside:
You don’t have to go through RegExp. You can pass any custom object that implements symbol.search and returns whatever you want.
Custom string. search method to call custom functions
The following example shows how we can make string.prototype. search call the search function of the custom Product class – thanks to symbol. search global Symbol.
The inner workings of Symbol. Search (CUSTOM BEHAVIOR)
- parsing
'barsoap. Search (soapObj)
; - will
"Barsoap"
Convert to StringNew String (" barsoap ")
- Due to the
soapObj
Already an object, do not perform any conversions - Calling the “barsoap” string object
search
Methods. search
Method calls the soapObj object internal methodSymbol.search
(It delegates the search back to the soapObj object) and passes"Barsoap"
As a parameter. Like this:soapObj[Symbol.search]("barsoap")
soapObj[Symbol.search]("barsoap")
Return index resultFOUND
tosearch
Delta function, and finally,search
returnFOUND
Go to our code.
Ok, let’s turn to Iterators.
Iterators and Iterables
Why is that?
In almost all applications, we are constantly working with lists of data that we need to display in a browser or mobile application. Usually we write our own methods to store and extract data.
But the problem is, we already have for-of loops and extension operators (…). To extract collections of data from standard objects such as arrays, strings, and maps. Why can’t we use these standard methods for our objects?
In the example below, we cannot use for-of loops or (…) Operator to extract data from the Users class. We must use a custom GET method.
But wouldn’t it be nice to be able to use these existing methods in our own objects? To achieve this, we need to lay down rules that all developers can follow and make their objects work with existing methods.
If they follow these rules to extract data from objects, those objects are called “iterations.”
The rule is:
- The main object/class should store some data.
- The main object/class must have the global “well-known” Symbols
symbol.iterator
As its properties, Symbols implements specific methods according to rules # 3 through # 6. - this
symbol.iterator
The method must return another object – an “iterator” object. - This “iterator” object must have a name called
next
Methods. - the
next
Methods should have access to the data stored in rule 1. - If we call
iteratorObj.next()
It should return some stored data in rule # 1 whether you want to return more values{value:<stored data>, done: false}
Still don’t want to return any data{done: true}
.
If all six rules are followed, the primary object from rule # 1 is said to be iterable. The object it returns is called an iterator.
Let’s look at how to create Users objects and iterate:
Important: If we pass an iterable (allUsers) for-of loop or extension operator,
[symbol.iterator]() will be called internally to get the iterator (such as allUsersIterator) and then use the iterator to extract the data.
So to some extent, all of these rules have a standard method for returning iterator objects.
The Generator function
Why is that?
There are two main reasons:
- Provide a higher level of abstraction for iteration
- Provide updated control flow to help solve problems such as “callback hell”.
Let’s look at the details.
Reason 1 – Iterative wrappers
Instead of following all these rules to make our class/object an iterable, we can simplify this by simply creating a “Generator” method.
Here are some important points about the Generator:
Generator
The method has an internal one*<myGenerator>
The new syntax,Generator
Functions have syntaxfunction * myGenerator(){}
.- Call the generator
myGenerator()
Returns an object that implements the Iterator protocol (rule)generator
Object, so we can use it asiterator
Return value out of the box. - The generator uses special yield statements to return data.
- The yield statement retains the state of the previous invocation and picks up where it left off.
- If yield uses it in the loop, it will only execute once each time we call the next() method on the callback iterator.
Case 1:
The following code shows how to implement next’s methods that follow all rules using generator methods (*getIterator()) instead of using symbol.iterator methods.
Example 2:
We can simplify it even further. Make the function generator (with * syntax) and return a value using a yield, as shown below.
Important note
"Iterator"
allUsers
generator
Generator objects have next methods in addition to methods throw and return, but we can use the returned object as an “iterator” for practical purposes.
Reason 2 – Provide better and updated control processes
Help provide new control flows, help us write programs in new ways and solve problems like “callback hell.”
Note that, unlike normal functions, generator functions can yield (store function state and return values) and be ready to take additional input values at the point at which they are generated.
In the following image, it returns the value every time you see yield. You can use generator.next(” some new value “) to use and pass the new value where it was generated.
The following example shows how control flow works in more detail:
Generator syntax and usage
The Generator function can be used in the following ways:
We can get more code after “yield” (unlike the “return” statement)
Just like the return keyword, the yield keyword also returns a value – but it allowed us to have code after the yielding keyword
You can have more than oneyield
throughnext
Method sends values back and forth to generators
The iterator next method can also pass the value back to the generator, as shown below.
In fact, this feature enables the Generator to eliminate “callback hell.” Learn more about this later.
This feature is also used heavily in libraries such as Redux-Saga.
In the following example, we use an empty next() call to invoke the iterator. Then, when we call it the second time, we pass 23 as the argument next(23).
next
Generator helps eliminate “callback hell”
If you have multiple asynchronous calls, you go into callback hell.
The following example shows how a library such as “co” can use the Generator feature, which allows us to pass values through the next method to help us write asynchronous code synchronously.
Notice how the CO function sends the result from the Promise back to the generator through next(result) steps 5 and 10.
"Co"
"Next (<someval>)"
lib
Ok, let’s continue async/await.
Asynchronous/AWAIT
Why is that?
Generators, as seen earlier, can help eliminate “callback hell”, but some third-party library co is needed to do it. But “callback hell” was such a big problem that the ECMAScript committee decided to create a wrapper for the Generator and push out the new keyword async/await.
Generators the difference between Generators and Async/Await is:
- Async/await is used
await
Rather thanyield
. await
Applies only toPromises
.Async / Await
useasync function
Keyword, rather thanfunction*
.
So async/await is basically a subset of Generators and has a new syntactic sugar.
The async keyword tells the JavaScript compiler to handle the function differently. The compiler pauses as soon as the keyword in the await function is reached. It assumes that the expression await returns a promise and waits until the promise is resolved or rejected before moving further.
In the example below, the getAmount function is calling two asynchronous functions getUser and getBankBalance. We can do this in promises, but it’s more elegant and simple to use async await.
ASYNC ITERATORS
Why is that?
This is a very common scenario where we need to call asynchronous functions in a loop. Therefore, in ES2018 (the completed proposal), the TC39 committee proposed a new Symbol symbol.asynciterator and a new construct, for-await-of, to help us loop asynchronous functions easily.
The main differences between a regular Iterator and an asynchronous Iterator are as follows:
The Iterator object
- The Iterator object
next()
Method returns the following value{value: ‘some val’, done: false}
- Usage:
Iterator.next () //{value: 'some val', done: false}
Async Iterator object
- Async Iterator object
Next ()
The method returns a Promise, which is later resolved to something similar{value: ‘some val’, done: false}
- Usage:
Iterator.next ().then(({value, done})=> {//{value: 'some val', done: false}}
The following example showsfor-await-of
How it works and how to use it.
The for – await – (ES2018)
conclusion
Symbol – Provides globally unique data types. Use them primarily as Object properties to add new behavior, so they don’t break standard methods like Object.keys and for-in loops.
The well-known Symbols- automatically generated by JavaScript, Symbols can be used to implement core methods in our custom objects
Iterables- is any object that stores a collection of data and follows certain rules so that we can use standard for-of loops and… The extension operator extracts data from it.
Iterators- Returns from Iterables and has a next method that actually extracts data from Iterables.
Generator – Provides a higher level of abstraction for Iterables. They also provide new control flows that solve problems like callback hell and provide building blocks Async/Await for things like that.
Async/Await- Provides a higher level of abstraction for the generator to specifically address the callback hell problem.
Async iterator – a new 2018 feature that helps loop through an array of asynchronous functions to get the result of each asynchronous function, just like in a normal loop.
Further reading
ECMAScript 2015+
- Below are examples of all the new features in ECMAScript 2016,2017 and 2018
- Check out these helpful ECMAScript 2015 (ES6) tips and tricks
- Five “bad” JavaScript fixes in ES6
- Are “classes” in ES6 the new “bad” part?