Asynchronous iterators combine the capabilities of iterators and the .
Asynchronous iterators are primarily intended for accessing data sources that use the asynchronous API. It can be some data that is loaded in parts, for example, over a network, from a file system, or from a database.
An asynchronous iterator is similar to a normal synchronous iterator, except that its next() method returns an object .
And from the Promise, in turn, the object { value, done } is returned.
To get data using asynchronous iterators, a for-await-of loop is used:
for await (variable of iterable) { // actions }
iterable – asynchronous or synchronous data source (array, Map, Set…)
!this form of loop can only be used in functions defined with an async statement.
Consider the simplest example, where an ordinary array acts as a data source:
const array = ["Alexander", "Timote", "Andrey"]; async function readData() { for await (const item of array) { console.log(item); } } readData(); // Alexander // Timote // Andrey
Here in the loop iterates over the dataSource array.
Looping through a data source (in this case, an array) with the [Symbol.asyncIterator]() method implicitly creates an asynchronous iterator.
And with each access to the next element in this data source, a Promise object is implicitly returned from the iterator, from which we get the current element of the array.
Creating an asynchronous iterator
In the example above, the asynchronous iterator was created implicitly. But we can also define it explicitly. For example, let’s define an asynchronous iterator that returns the elements of an array:
const generatePerson = { [Symbol.asyncIterator]() { return { index: 0, people: ["Alexander", "Timote", "Andrey"], next() { if (this.index < this.people.length) { return Promise.resolve({ value: this.people[this.index++], done: false }); } return Promise.resolve({ done: true }); } }; } };
So, the generatePerson object is defined here, in which only one method is implemented – [Symbol.asyncIterator](), which, in fact, represents an asynchronous iterator.
The implementation of an asynchronous iterator (as in the case of a synchronous iterator) allows you to make a generatePerson object sorted.
If all elements of the array have already been received, then return the Promise with the object { done: true }:
return Promise.resolve({ done: true });
Getting data from an asynchronous iterator
As with a regular iterator, we can refer to the asynchronous iterator itself:
generatePerson[Symbol.asyncIterator]();
And explicitly call its next() method:
generatePerson[Symbol.asyncIterator]().next(); // Promise
This method returns a Promise, on which you can call the then() method and process its value:
generatePerson[Symbol.asyncIterator]() .next() .then((data) => console.log(data)); // {value: "Alexander", done: false}
The object obtained from the promise represents the {value, done} object, from which the actual value can be obtained through the value property:
generatePerson[Symbol.asyncIterator]() .next() .then((data) => console.log(data.value)); //Alexander
Since the next() method returns a Promise, we can use the await operator to get values:
async function printPeople() { const peopleIterator = generatePerson[Symbol.asyncIterator](); while (!(personData = await peopleIterator.next()).done) { console.log(personData.value); } } printPeople();
Iterating with for-await-of:
const generatePerson = { [Symbol.asyncIterator]() { return { index: 0, people: ["Tom", "Sam", "Bob"], next() { if (this.index < this.people.length) { return Promise.resolve({ value: this.people[this.index++], done: false }); } return Promise.resolve({ done: true }); } }; } }; async function printPeople() { for await (const person of generatePerson) { console.log(person); } } printPeople();
Because the generate Person object implements the [Symbol.asyncIterator]() method, we can iterate over it with a for-await-of loop.
Accordingly, with each call in the loop, the next() method will return a promise with the next element from people array.
Another simple example is getting numbers:
const generateNumber = { [Symbol.asyncIterator]() {//create an iterator return { current: 0,//set the initial value end: 10,//set the final value next() {// if (this.current <= this.end) {//if the initial value is less than the final value return Promise.resolve({ value: this.current++, done: false });//in the result of the promise, we return an object with the value and property done:false, which will indicate that the iterator should continue to iterate over the numbers } return Promise.resolve({ done: true });//when the value is greater than the final one, we return done: true and finish the iterator } }; } }; async function printNumbers() {//create a new asynchronous function for await (const n of generateNumber) {//iterate our asynchronous iterator console.log(n); } } printNumbers();//launch the asynchronous function
Asynchronous generators
Asynchronous iterators open the way for us to create asynchronous generators.
Asynchronous generators allow us to use the await operator and receive and return data in an asynchronous manner.
To define an asynchronous generator, the generator function is preceded by the async statement
async function* generatorName() { yield returned_value; }
Consider the simplest generator:
async function* generatorNumber() { yield 5; }
Here, an asynchronous generator generatePersonAsync is defined, in which a single value is returned – the number 5.
A feature of an asynchronous generator is that when its next() method is called, a Promise object is returned.
And the resulting Promise object, in turn, returns an object with two properties {value, done}, where value actually stores the returned value, and done indicates whether more data is available in the generator.
For example, take the above defined asynchronous generator:
async function* generateNumber() { yield 5; } const personGenerator = generateNumber(); generateNumber.next(); // Promise
Here, using the next() method, we get a Promise. Further, through the then() method, we can get an object from the promise:
const newNumber = generateNumber(); newNumber.next() .then(data => console.log(data)); // {value: "5", done: false}
Receive data:
console.log(data.value); // 5
Using the await operator from the generator’s next() method, we can get the data:
async function* generatePersonAsync() { yield "Alexander"; yield "Timote"; yield "Sergey"; } async function printPeopleAsync() { const personGenerator = generatePersonAsync(); while (!(person = await personGenerator.next()).done) { console.log(person.value); } } printPeopleAsync();
Since an asynchronous generator represents an asynchronous iterator, the generator data can also be obtained through a loop for-await-of:
async function* generatePersonAsync() { yield "Alexander"; yield "Timote"; yield "Sergey"; } async function printPeopleAsync() { const personGenerator = generatePersonAsync(); for await (person of personGenerator) { console.log(person); } } printPeopleAsync(); // Alexander // Timote // Sergey
The main advantage of asynchronous generators is that we can use the await operator in them and receive data from data sources that use the asynchronous API.
async function* generatePersonAsync(people) { for (const person of people) yield await new Promise(resolve => setTimeout(() => resolve(person), 2000)); } async function printPeopleAsync(people) { for await (const item of generatePersonAsync(people)) { console.log(item); } } printPeopleAsync(["Alexander", "Timote", "Sergey"]);