Some JavaScript (ECMAScript) features are easier to understand than others. Generators look strange — like Pointers in C/C++. The Symbols type looks like both primitives and objects.
These features are all interrelated and built upon each other. So you can’t understand one in isolation from the other.
So in this article I’ll cover Symbols, global Symbols, Iterators, Generators, Async/Await, and Async Iterators. First I’ll explain “why” they’re here, and then I’ll use some useful examples to show how they work.
This is a fairly high order problem, but it’s not complicated. This article should give you a good understanding of all these concepts.
Ok, let’s get started.
“Symbols” and what’s known as “Symbols”
Symbols
In ES2015, a new (sixth) data type was created, named Symbol.
Why is that?
Here are three main reasons:
Reason #1 — Adding new kernel features that are backward compatible
JavaScript developers and the ECMAScript Committee (TC39) needed a way to add new object attributes without breaking existing methods, such as for… In loop or JavaScript method object.keys.
For example, if I have an Object, var myObject = {firstName:’raja’, lastName:’rao’}, if I call object.keys (myObject), It will return [firstName, lastName].
Now if I add a property, that is, newProperty to myObject, if I call Object.keys(myObject), then I should still return the previous value, [firstName, lastName], You don’t want to return firstName, lastName, newProperty. So how do you do this?
We couldn’t do this before, so we created a new data type called Symbols.
If you add newProperty as a symbol, object.keys (myObject) ignores this property (since it will not be recognized) and still returns [firstName, lastName]!
Reason #2 — Avoid naming conflicts
They also want to keep these properties unique. This way, they can constantly add new attributes to the global (and can add object attributes) without worrying about naming conflicts.
For example, you have an object in which you are adding a custom toUpperCase to the global array. prototype.
Now imagine that you load another library (or ES2019 issued), it has a different version of the Array. The prototype. The toUpperCase. Then your function might crash due to a naming conflict.
Copy the code
So how do you resolve this naming 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 — The “Well known” Symbols allows hooks to be called to kernel methods
Suppose you want some core functions, such as string.prototype. search to call your custom function. That is, ‘someString ‘.searchh(myObject); You should call myObject’s search function and pass in ‘someString’ as an argument! How do we do that?
This is a set of global symbols proposed by ES2015, known as “well-known” Symbols. And as long as your object has one of these symbols as a property, you can redirect kernel functions to call your own custom functions!
We won’t talk about this for now, but I’ll cover all the details later in this article. But first, let’s see how Symbol works.
Create Symbols
You can create a Symbol by calling a function/object named Symbol global. This function returns a value of data type symbol.
// mySymbol is also a symbol data type var mySymbol = symbol ();Copy the code
Note: Symbols may look like objects because they have methods, but they are not — they are primitives. You can think of them as “special” objects that have some resemblance to specific objects, but not like regular objects.
For example: Symbols have methods similar to objects, but unlike objects, they are immutable and unique.
Symbols cannot be created using the “new” keyword
Since symbols is not an object and the new keyword should return an object, we cannot use new to return a Symbols data type.
var mySymbol = new Symbol(); //throws error
Copy the code
Symbols has “description”.
Symbols can contain a description — it is only used to document the intent.
// The mySymbol variable now has a unique value of "symbol", which is described as "some text" const mySymbol = symbol ('some text');Copy the code
Symbols are unique
const mySymbol1 = Symbol('some text');
const mySymbol2 = Symbol('some text');
mySymbol1 == mySymbol2 // false
Copy the code
If we use the “symbol. for” method, Symbols behave like singletons
If you do not create a Symbol through Symbol(), you can call symbol.for (
). It requires passing a “key” (string) to create a Symbol. If the symbol corresponding to this key already exists, the previous symbol is simply returned! So if we call the symbol. for method, it will behave like a singleton.
var mySymbol1 = Symbol.for('some key'); //creates a new symbol
var mySymbol2 = Symbol.for('some key'); // **returns the same symbol
mySymbol1 == mySymbol2 //true
Copy the code
The actual use case for using.for is to create a Symbol in one place and then access the same Symbol elsewhere.
Warning: symbol. for will make symbols non-unique, so if the keys are the same, you will end up overwriting the values inside. So avoid that if you can!
Symbol’s “description” vs. “key”
To make things clear, if you don’t use symbol. for, Symbols are unique. However, if you use it, then if your key is not unique, neither is the symbol returned.
var mySymbol1 = Symbol('some text'); Var mySymbol2 = Symbol('some text'); // Create a unique Symbol, described as some text var mySymbol3 = symbol. for('some text'); Var mySymbol4 = symbol. for('some text'); // Return the same symbols stored in mySymbol3. // Only the following expression returns true, because they all use the same key, "some text" mySymbol3 == mySymbol4 //true //... All other symbols are different. MySymbol1 == mySymbol2 //false mySymbol1 == mySymbol3 //false mySymbol1 == mySymbol4 //falseCopy the code
Symbols can be object property keys.
This is one of the oddest aspects of Symbols — and one of the most confusing. Although they look like objects, they are primitives. We can associate symbol to an object as a property key, just like String.
In fact, this is the main way Symbols are used — as object properties!
const mySymbol = Symbol('some car description') const myObject = {name:'bmw'} myObject[mySymbol] = 'This is a car'; Console. log(myObject[mySymbol]) //'This is a car'Copy the code
Note: Object properties that use symbols are called “key properties”.
Parenthesis operators vs. dot operators
Since dot operators can only be used for string attributes, you cannot use dot operators here, so you should use parenthesis operators instead.
let myCar = {name: 'BMW'}; let type = Symbol('store car type'); myCar[type] = 'A_luxury_Sedan'; let honk = Symbol('store honk function'); myCar[myFunction] = () => 'honk'; // Usage: myCar. Type; // error myCar[type]; //'store car type' myCar.honk; // error myCar[honk]; // 'honk'Copy the code
Three main reasons for using Symbols — review
Now let’s review three main reasons to see how Symbols works.
Reason #1. For loops and other methods,? Symbols are invisible
The for-in loop in the following example iterates through the object obj, but does not know (or ignores) prop3 and prop4 because they are symbols.
var obj = {};
obj['prop1'] = 1;
obj['prop2'] = 2;
// Lets add some symbols to the object using "brackets"
// (note: this MUST be via brackets)
var prop3 = Symbol('prop3');
var prop4 = Symbol('prop4');
obj['prop3'] = 3;
obj['prop4'] = 4;
for (var key in obj) {
console.log(key, '=', obj[key])
}
// The above loop prints...
// (doesn't know about props3 and prop4)
// prop1 = 1
// prop2 = 2
// however, you can access props3 and prop4 directly via brackets
console.log(obj[prop3]) //3
console.log(obj[prop4]) //4
Copy the code
Here is the Object. The keys and Object. GetOwnPropertyNames ignore Symbols attribute name another example.
const obj = { name: 'raja' }; //add some symbols.. obj[Symbol('store string')] = 'some string'; obj[Symbol('store fun')] = () => console.log('function'); // symbol key properties are ignored by many other methods console.log(Object.keys(obj)); //['name'] console.log(Object.getOwnPropertyNames(obj)); //['name']Copy the code
Reason #2. Symbol is unique
Suppose you need a global Array object called array.prototype. includes. It will conflict with JavaScript’s (ES2018) native includes method out of the box. How do you add it without conflict?
First, create a variable named includes and assign a symbol to it. This variable (now a Symbol) is then added to the global Array using parenthesis notation. Assign any function you want.
Finally, the function is called using parenthesis notation. Note, however, that you must pass the real symbol in parentheses, not a string, like: arr[includes]().
var includes = Symbol('will store custom includes methods');
// Add it to global Attay.prototype
Array.prototype[includes] = () => console.log('inside includes funs');
//Usage:
var arr = [1, 2, 3];
// The following each call the ES2018 includes methods
console.log(arr.includes(1)); // true
console.log(arr['includes'](1)); // true; here is a string
// The following calls the custom includes methods
console.log(arr[includes]()); // 'inside includes funs'; here includes is a symbol
Copy the code
Reason #3. The well-known Symbols (” global “Symbols)
By default, JavaScript automatically creates a bunch of symbol variables and assigns them to the global Symbol object (yes, we use the same symbol () to create symbols).
In ECMAScript 2015, these symbols are then added to the core methods of core objects such as arrays and strings, such as String.prototype.search and String.prototype.replace.
Give some examples of Symbols: 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 our custom functions instead of internal ones.
For example: symbol.search
For example, the string.prototype. search public method of String searches for a regExp or String and returns the index when it is found.
'rajarao'.search(/rao/); //4 'rajarao'.search('rao'); / / 4Copy the code
In ES2015, it first detects whether the symbol.search method is implemented when querying regExp (regExp object). If so, call this function and leave the work to it. And a core object like RegExp, which implements Symbol of symbol.search, does the job.
The inner workings of symbol.search (default behavior)
- parsing
'rajarao'.search('rao')
; - Convert “rajarao” to a String
New String (" rajarao ")
- Convert “RAO” into a RegExp object
New Regexp (" rao ")
- To call the “rajarao” String
search
Methods. search
Method that calls the rao object internallySymbol.search
Method (returns search to the “rao” object) and passes into “rajarao”. Something like this:"rao"[Symbol.search]("rajarao")
"rao"[Symbol.search]("rajarao")
The results for4
Index returns tosearch
Method, and finally,search
将4
Let’s go back to our code.
The following pseudocode snippet shows how the code works inside:
// pseudo code for String class
class String {
constructor(value){
this.value=value;
}
search(obj) {
// call the obj's Symbol.search method and pass my value to is
obj[Symbol.search](this.value);
}
}
// pseudo code for RegExp class
class RegExp {
constructor(value){
this.value=value;
}
search(obj) {
// call the obj's Symbol.search method and pass my value to is
Symbol.search(string){
return string.indexOf(this.value);
}
}
}
// inner workings...
// 'rajarao'.search('rao');
// step1: convert 'rajarao' to String Object... new String('rajarao');
// step2: 'rao' is a string, so convert it to RegExp object... new RegExp('rao');
// step3: call 'rajarao's 'search' method and pass 'rao' object
// step4: call 'rao' RegExp object's [Symbol.search] method
// step5: return result
Copy the code
But the beauty is that you no longer have to pass regexps. You can pass any object that implements symbol.search and return whatever you want, and it will continue to work.
Let’s take a look.
Custom string. search method to call our function
The following example shows how we call the search method of our Product class using string.prototype. search — thanks to the global Symbol symbol. search.
class Product { constructor(type) { this.type = type; } // implement search funture [Symbol.search](string) { return string.indexOf(this.type) >= 0 ? 'FOUND' : 'NOT_FOUND'; } } var soapObj = new Product('soap'); 'barsoap'.search(soapObj); //FOUND 'shampoo'.search(soapObj); //NOT_FOUNDCopy the code
The inner workings of symbol.search (custom behavior)
- parsing
'barsoap'.search(soapObj)
;
Convert “barsoap” to String object New String(“barsoap”)
Since soapObj is already an object, do not make any transformations
Call the search method of the “barsoap” string object.
The search method internally calls the symbol.search method of the “barsoap” object (that is, it re-delegates search to the “barsoap” object) and passes “barsoap”. Like this: soapObj[symbol.search](“barsoap”)
SoapObj [symbol.search](“barsoap”) returns the index result as FOUND to the search function, and finally, search returns FOUND to our code.
Hopefully now you have a better grasp of the Symbols concept.
Okay, let’s move on 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 app. Usually we write our own methods to store and extract this data.
But the truth is, we already have operators like for-of and spread (…). Such a standard method 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 a for-of loop or the spread operator to extract data from our Users class. We must use a custom get method.
// BEFORE: // We can't use standard 'for-of' loop or "..." spread operator // to extract each user from Users. class Users { constructor(users) { this.users = users; } get() {// this is not standard return this.users; } } const allUsers = new Users([ {name: 'raja'}, {name: 'john'}, {name: 'matt'} ]); //allUsers.get() works ,but we can't do the following... for (const user of Users) { console.log(user) } // "TypeError:Users is no iterable" // We also can't do the following... [...allUsers]; // "TypeError:Users is no iterable"Copy the code
But wouldn’t it be nice to use these existing methods in our own objects? To accomplish this idea, we need to have some rules that all developers can follow and make their objects use these existing methods.
If they follow these rules to pull data from their objects, those objects are called “iterables.”
Here are the rules:
- The main object/class should store some data.
- The main object/class must have a global “known” symbol, i.e
symbol.iterator
As its property, it then implements a specific method according to each rule from rule #3 through rule #6. symbol.iterator
Method must return another object — an “iterator” object.- The “iterator” object must have a name
next
Methods. next
Methods should have access to the data stored in rule #1.- If we call
iteratorObj.next()
, should return the data stored in rule #1, use the format if you want to return more values{value:<stored data>, done: false}
If you don’t want to return more than one value, use the format{done: true}
。
If all six rules are sequenced, the main object in rule #1 is called an iterable. The object it returns is called an iterator.
Let’s see how we can make our Users object iterable:
//AFTER: //User is an "iterable",because it implementsa "Symbol.iterator" method //that returns an object with "next" method and returns values as per rules. class Users { constructor(users) { this.users = users; } // Have Symbol.iterator symbol as a property that stores a method [Symbol.iterator]() { let i = 0; let users = this.users; //this returned object is called an "iterator" return { next() { if (i < users.length) { return { done: false, value: users[i++] }; } return { done: true }; }}; } } //allUsers is called an "iterarable" const allUsers = new Users([ { name: 'raja' }, { name: 'john' }, { name: 'matt' }, ]); //allUsersIterator is called an "iterator" const allUsersIterator = allUsers[Symbol.iterator](); //next method returns the next value in the stored data allUsersIterator.next(); //{done:false,value:{name:'raja'}} allUsersIterator.next(); //{done:false,value:{name:'john'}} allUsersIterator.next(); //{done:false,value:{name:'matt'}} //Using in for-of loop for (const u of allUsers) { console.log(u.name); } //prints.,raja,john,matt //Using in spread operator console.log([...allUsers]); //prints.. [{name:'raja1},{name:'john'},{name:'matt'}]Copy the code
Important note: If we pass an iterable (allUsers) for-of loop or expand identifiers, they internally call
[symbol.iterator]() to get iterators (like allUsersIterator), Iterators are then used to fetch the data.
So to some extent, all of these rules have a standard way to return an iterator object.
Generator function
Why is that?
Two main reasons are as follows:
- Provides a high-level abstraction of iterables
- Provide new process controls to improve situations like “callback hell.”
We will elaborate below.
Reason #1 — Wrappers for Iterables
To make our class/object program an iterable, in addition to following all these rules, we can simplify these operations by simply creating functions called “generators.”
Some important points about generators are as follows:
- The generator function has a new one in the class
*<myGenerator>
Syntax, and generator functions have syntaxfunction * myGenerator(){}
。 - Call generator
myGenerator()
Returns agenerator
Object, which is also implementediterator
Protocol (rule), so we can use it as a ready-to-useiterator
The return value. - The generator uses a unique
yield
Declaration to return data. yield
Statement tracks the previous call and picks up where it left off.- If you use it in a loop
yield
, every time we call in the iteratornext()
Method, it will run only once.
Example 1:
The following code shows you how to use the generator method (*getIterator()) instead of the symbol.iterator method and implement the next method that follows all the rules.
//Intead of making our object an Iterable,we can simply createa //generator (function* syntax) and return an Iterator to extract data, class Users { constructor(users) { this.users = users; this.len = users.length; } //is a generator and Itreturns an Iterator! * getIterator() { for (let i in this.users) { yield this.users[l]; //although inside loop,"yield" runs only once per call } } } const allUsers = new Users([ { name: 'raja' }, { name: 'john' }, { name: 'matt' }, ]); const allUsersIterator = allUsers.getIterator(); // //next method returns the next value in the stored data console.log(allUsersIterator.next()); //{done:false,value:{name:'raja'}} console.log(allUsersIterator.next()); //{done:false,value:{name:1john'}} console.log(allUsersIterator.next()); //{done:false,value:{name:'matt'}} console.log(allUsersIterator.next()); //{done:true,value:undefined> //Using in for-of loop for (const u of allUsersIterator) { console.log(u.name); } //prints.,raja,john,matt //Using in spread operator console.log([...allUsersIterator]); //prints.. [{name:'raja'},{name:'john'},{name:'matt'}]Copy the code
Example 2:
You can simplify it further. Create a function as a generator (using the * syntax) and use yield to return one value at a time, as shown below.
//Users is now a generator! and it returns an iterator function* Users(users) { for (let i in users) { yield users[i++]; //although inside loop,"yield" runs only once per next() call } } //allUsers is now an Iterator! const allUsers = Users([{ name: 'raja' }, { name: 'john' }, { name: 'matt' }]); //next method returns the next value in the stored data console.log(allUsers.next()); //{done:false,value:{name:1raja1}} console.log(allllsers.next()); //{done:false,value:{name:1john1}} console.log(allUsers.next()); //{done:false,value:{name:1matt1}} console.log(allllsers.next()); //{done:true, value:undefined} //Using in for loop for (const u of allUsers) {console.log(u.name); } //prints.. raja,john,matt //Using in spread operator console.log([...allUsers]); //prints.. [{name:1raja'}, {name:' John '}, {name:1matt1}]Copy the code
Important note: Although I used the word “iterator” for allUsers in the example above, it is indeed a generator object.
In addition to the next method, generator also has throw and return methods! But for practical purposes, we can use the returned object as an “iterator.”
Reason 2 – Provide better and updated process control
Providing new flow controls can help us write programs in new ways and solve problems like “callback hell.”
Note that, unlike normal functions, a generator function can yield (store the state and return values of the function) and is known to be ready to obtain additional input values when it is expressde.
In the figure below, you can return this value every time you see yield. You can use generator.next(” some new value “) and pass in the new value when expressde.
The following example shows more concretely how process control works:
function* generator(a, b) { //return result of a + b; //also store any *new* input in k (not the result of a + b) let k = yield a + b; let m = yield a + b + k; yield a + b + k + m; } var gen = generator(10, 20); //Get me value of a + b... //note: done is "false" because there are more "yield" statements left! console.log(gen.next()); //{ value: 30, done: false } //... at this point, the function remains in the memory with values a and b //and if you call it again with some number, it starts where it left off. //assign 50 to "k" and return result of a + b + k console.log(gen.next(50)); //{ value: 80, done: false } //... at this point, the function remains in the memory with values a, b and k //and if you call it again with some number, it starts where it left off. //assign 100 to "k" and return result of a+b+k+m console.log(gen.next(100)); //{ value: 80, done: false } //... at this point, the function remains in the memory with a, b, k, and m //But since there are no more yield calls, it returns undefined. //call next again gen.next(); //{ value: undefined, done: true }Copy the code
Generator syntax and usage
Generator functions can be called using the following methods:
//SYNTAX AND USAGE...
//As Generator functions
function* myGenerator() { }
//or..
function* myGenerator() { }
//or..
function* myGenerator() { }
//As Generator Methods
const myGenerator = function* () { }
//Generator arrow functions - ERROR
//Can't use it with arrow function
let generator = * () => { }
//Inside ES2015 class..
class myClass() {
*myGenerator() { }
}
//Inside object literal
const myObject = {
*myGenerator(){ }
}
Copy the code
We can write more code after “yield” (unlike “return” statements)
Just like the return keyword, the yield keyword will also return values — but it will allow for code after the yielding keyword!
function* myGenerator() {
let name = 'raja';
yield name;
console.log('you can do more stuff after yield')
}
//generator returns an iterator object
const mylterator = myGenerator();
//call next() the first time..
//returns { value: 'raja1, done: false }
console.log(myIterator.next());
//call next() the 2nd time..
//Prints: 'you can do more stuff after yield'
//and returns: { value: undefined, done: true }
console.log(myIterator.next());
Copy the code
You can have multiple yields
function* myGenerator() {
let name = 'raja';
yield name;
let lastName = 'rao';
yield lastName;
}
//generator returns an iterator object
const mylterator = myGenerator();
//call next() the first time..
//returns { value: 'raja1, done: false }
console.log(myIterator.next());
//call next( ) the 2nd time..
//returns: { value: 'rao1, done: false }
console.log(myIterator.next());
Copy the code
Pass values back and forth to the generator using the “Next” method
Iterators’ next method can also pass values to generators, as written below.
In fact, this feature allows the generator to eliminate “callback hell.” You’ll learn a little bit more about this.
This feature is also used heavily in libraries, such as Redux-Saga.
In the following example, we call the iterator with an empty next() call to get the problem. Then, when we call next(23) the second time, we pass 23 as the value.
function* profUeGenerator() { //first yield 'How old are you? ' for the first next( ) call //Store the value from the 2nd next(<input>) value in answer let answer = yield 'How old are you? '; //based on the value stored in the answer, yield either 'adult' or 'child' if (answer > 18) { yield 'adult'; } else { yield 'child '; } } //generator returns an iterator object const mylterator = profileGenerator(); console.log(myIterator.next()); //{ value: 'How old are you? ', done: false } console.log(myIterator.next(23)); //{ value: 'adult', done: false }Copy the code
Generators help eliminate “callback hell”
You know if we had multiple asynchronous calls, we would go into callback hell.
The following example shows how libraries like “CO” can take advantage of generator features to let us pass values through the next method to help us write asynchronous code synchronously.
Notice in steps 5 and 10 how the co function passes the result from the promise back to the generator via next(result).
co(function* () {
let post = yield Post.findByID(10);
let comments = yield post.getComments();
console.log(post, comments);
}).catch(function (err) {
console.error(err);
});
//Step 1: The "co" library takes the generator as argument,
//Step 2: Calls the async code "Post.findByID(10)"
//Step 3: "Post.findByID(10)" returns a Promise
//Step 4: Waits for the Promise to resolve,
//Step 5: Once the Promise returns result, calls "next(result)"
//Step 6: Stores the result in "post" variable.
//Step 7: Calls the async code "post.getComments( )";
//Step 8: "post.getComments( )" returns a Promise
//Step 9: Waits for the Promise to resolve
//Step 10: Once the Promise returns result, calls "next(result)"
//Step 11: Stores the result in "comments" variable.
//Step 12: console.log(post, comments);
Copy the code
Ok, let’s talk about asynchrony/wait.
Asynchronous/wait
Why is that?
As you saw earlier, generators help eliminate “callback hell,” but you’ll need some third-party libraries like CO to do it. But “callback hell” is still a big problem, and the ECMAScript committee decided to create a wrapper around this problem for generators only, along with a new keyword async/await.
The difference between generator and asynchronous/wait is as follows:
- Async/wait uses await instead of yield.
- Await is only good for Promises.
- It USES
async
Function keyword instead offunction*
。
Thus async/await is an important subset of Generators that contains a new Syntactic sugar (Syntactic sugar).
The async keyword tells the JavaScript compiler to treat such functions differently. The compiler pauses whenever it encounters the await keyword in a function. Suppose the expression after await returns a promise and waits for the promise to be processed or rejected before proceeding further.
In the example below, the getAmount function calls the two asynchronous functions getUser and getBankBalance. We could do the same in a promise, but using async await is more elegant and simple.
//Instead of.. //ES2015 Promise function getAmount(userId) { getUser(userId) .then(getBankBalance) .then(amount => { console.log(amount); }); } //Use.. //ES2017 async function getAmount2(userId) { var user = await getUser(userId); var amount = await getBankBalance(user); console.log(amount); } getAmount('1'); / / $1000 getAmount2 (' 1 '); // $1,000 function getUser(userId) {return new Promise(resolve => {setTimeout(() => {resolve(' John ')); }, 1000); }); } function getBankBalance(user) { return new Promise((resolve, Reject = > {setTimeout () () = > {the if (user = = 'John') {resolve (' $1000 '); } else { reject('unknown user'); }}, 1000); }); }Copy the code
Asynchronous iterator
Why is that?
It is very common for us to need to call asynchronous functions in a loop. So in ES2018 (completed proposal), the TC39 committee proposed a new Symbol of symbol. asyncIterator, as well as a new for-await-of structure to help us simplify loiterating over asynchronous functions.
The main differences between ordinary iterators and asynchronous iterators are as follows:
Iterator object
The iterator object’s next() method returns values like {value: ‘some val’, done: false}
Iterator.next () //{value: ‘some val’, done: false}
Asynchronous iterator object
The next() method of an asynchronous iterator object returns a Promise that resolves to something like {value: ‘some val’, done: false}
Iterator.next ().then(({value, done})=> {//{value: ‘some val’, done: false}}
The following example shows how for-await-of works and how you can use it.
const promises = [ new Promise(resolve => resolve(1)), new Promise(resolve => resolve(2)), new Promise(resolve => resolve(3)), ]; //you can simply loop over an array of functions //that return promise and get the value in the loop async function test() { for await (const p of promises) { console.log(p); } } test(); / / 1, 2, 3Copy the code
conclusion
Symbols — Provides globally unique data types. You use them primarily as Object properties to add new behaviors, so as not to break standard methods like Object.keys and for-in loops.
The well-known Symbols — automatically generated by JavaScript — can implement core methods in our custom objects.
Iterables — any object that stores a collection of data that follows certain rules so that we can use standard for-of loops and… Expand the identifier to extract data from it.
Iterators — with iteration returns and next methods — are the methods that actually get data from iterable.
Generators — Provide a higher level of abstraction for Iterables. They also provide new control flows that solve problems like callback hell and provide building blocks for things like Async/Await.
Async/Await(Async/Await) — Provides generators with high-level abstract methods specifically to solve the callback hell problem.
Async Iterators — a new feature in ES2018 that allows loops to access an array of asynchronous functions to retrieve the results of each asynchronous function, just like a normal loop.
That’s all!
read
ECMAScript 2015+
- Examples of all the new features in ECMAScript 2016, 2017 and 2018
- Check out these useful ECMAScript 2015 (ES6) tips
- 5 types of JavaScript Bad Parts fixed in ES6
- Is “Class” in ES6 a new bug?
My other posts can be found here.
English text links: medium.freecodecamp.org/some-of-jav…