JavaScript is partly a functional language.
To learn JavaScript, we got to learn the functional parts of JavaScript.
In this article, we’ll look at JavaScript generators.
Callback Hell
If we some functions like:
let sync = () => {
//..
}
let sync2 = () => {
//...
}
let sync3 = () => {
//...
}
and each function is synchronous, then we can call it one by one and compose them our way.
But if they’re async, then we can’t call one by one.
An async function may have a callback to let us call the callback when we get the result:
let async1 = (fn) => {
//...
fn( /* result data */ )
}
let async2 = (fn) => {
//...
fn( /* result data */ )
}
let async3 = (fn) => {
//...
fn( /* result data */ )
}
Then if we want to call them sequentially, we’ve to write something like:
async1(function(x) {
async2(function(y) {
async3(function(z) {
//...
});
});
});
As we can see, there’s a lot of nesting in our code.
We got to make this cleaner.
Generators
We can clean up this code with generators.
To create a generator function, we can write:
function* gen() {
return 'foo';
}
A generator function is indicated by the function*
keyword.
Then we can call it to create a generator:
let generator = gen();
The generatorResult
object has the next
method to return the value we yielded.
The returned object has the value
and done
properties.
So we can write:
generator.next().value
and we get 'foo'
.
Calling next
for a second time will return an object with the value
being undefined
.
The yield
keyword is a new keyword that will bring value to the next
method.
The value of each yield
statement will return in the sequence they’re listed.
yield
pauses the execution of the function and sends back the result to the caller.
Then next time next
is called, the next
yield statement is run.
So if we have:
function* gen() {
yield 'first';
yield 'second';
yield 'third';
}
let generator = gen();
The first generator.next()
call returns:
{value: "first", done: false}
Then 2nd call returns:
{value: "second", done: false}
The 3rd call returns:
{value: "third", done: false}
The 4th call returns:
{value: undefined, done: true}
which indicates that the generator has no more values to return.
The done
property indicates whether the generator bis done with returning all the values.
We know when to stop calling next
once done
is true
.
Passing Data to Generators
We can also pass data to generators.
To do that, we create a generator function with a yield
statement that has no value after it.
For example, we can write:
function* fullName() {
const firstName = yield;
const lastName = yield;
console.log(`${firstName} ${lastName}`);
}
We have the yield
statement without a value after it, so it’ll accept values we pass into next
.
Then we can use it by writing:
const fn = fullName();
fn.next()
fn.next('mary')
fn.next('jones')
We create the generator function.
Then we call next
to start the generator.
Once we did that, we can start passing values into the generator.
We call:
fn.next('mary')
to pass in a value to the first yield
statement.
Then we do the same with the 2nd one with:
fn.next('jones')
Once we did that, we get 'mary jones'
from the console log.
Async Code and Generators
The async
and await
syntax is created based on generators.
For instance, we can use it by writing:
const getData = async () => {
const res = await fetch('https://api.agify.io/?name=michael')
const data = await res.json();
console.log(data);
}
We have the async
and await
syntax.
await
pauses the execution of getData
until the result is present.
So it acts like yield
.
Now we run our async code line by line.
The only difference is that await
only works with promises.
Conclusion
We can use generators to return items in sequence.
The function is paused after a result is returned and resumed when the next result is requested.
The async
and await
syntax for promises is based on the generator syntax.