How to use Facebook’s React Library to Build UIs, Part 2
Front-End Full-Stack WebIn my previous post, we created a simple UI component with Facebook’s React library. Now, we’ll create data flows between multiple components.
On the internet, there are two things that are totally played out: Rick Astley and
the Fibonacci sequence. But finally, in one blog post, they have been combined. Well. Sort of.
This post is an introduction to JavaScript’s generators, added in ES6, the latest version of JavaScript. This update is also known as “ES2015.”
In a nutshell, generators are functions that can be paused
and resumed. They are useful for things like incrementally
creating very large sequences.
More importantly, they create sequences that are iterators.
If you’ve worked with arrays, you have likely had to iterate
through the elements of that array. There are a few ways you can do this in JavaScript.
One way is to use the very boring (but very fast) for-loop:
let numbers = [0, 1, 1, 2, 3, 5, 8, 13];
for (let index=0; index < numbers.length; index++) {
console.log(`This number is: ${numbers[index]}`);
}
That snippet of code prints out the first few Fibonacci numbers to the console, as you might expect.
Another way to go through the elements is to use the forEach
method
on an array. When you pass a function argument to forEach
, forEach
will visit each element in the array. As it visits each element, it invokes your
function argument and passes it that element.
let numbers = [0, 1, 1, 2, 3, 5, 8, 13];
numbers.forEach((number) => {
console.log(`This number is: ${number}`);
});
Here, we’ve passed an anonymous arrow function to forEach
, but you can also use named functions or regular (non-arrow) anonymous functions. The arrow function expects
to receive the current item being visited by forEach
and gives it the
label number
, which is then used inside the body of the arrow function.
Some developers prefer forEach
because they find it more expressive than
a for-loop.
ES6 provides another way of doing iteration: by using for..of
.
let numbers = [0, 1, 1, 2, 3, 5, 8, 13];
for (let number of numbers) {
console.log(`This number is: ${number}`);
}
Like a for-loop, you use the for
keyword. But inside of the parentheses, you specify a variable that will point to the current element. Inside the curly braces, you write the body as usual.
Ok, great! Yet another way to work with array elements. Admittedly, it’s
not all that exciting. But, for..of
isn’t limited to arrays. You can
create custom iterators that work with for..of
.
There are two new pieces of syntax that let you create custom iterators:
function *
and yield
.
Here’s how you would write our not-so-exciting example using that syntax:
function * numbers() {
yield 0;
yield 1;
yield 1;
yield 2;
yield 3;
yield 5;
yield 8;
yield 13;
}
for (let number of numbers()) {
console.log(`This number is: ${number}`);
}
First, you declare a function using the function *
syntax.
This tells the JavaScript engine that this function will generate
an iterator. You could say that this is a…generator function.
Some developers prefer to put the *
right beside the
function name, like so:
function *numbers() {
yield 0;
yield 1;
yield 1;
yield 2;
yield 3;
yield 5;
yield 8;
yield 13;
}
(That is the style that will be used for the rest of this blog post.)
Inside of your generator, you yield
at least one value.
When you call a generator, it returns an iterator.
for..of
knows to “ask” an iterator for its next value.
The iterator yield
s its values until there are no more yield
statements.
Obviously, you wouldn’t want to use this syntax to spit
out a bunch of values you could just put into an array.
Let’s continue with the tried and true Fibonacci sequence in the next example.
One of the ways you might use generators is to create a sequence
of calculated values. Here is a generator that produces a
sequence of Fibonacci numbers.
function *fibonacci() {
var n1 = 0;
var n2 = 1;
while(true) {
yield n1; // yield the first number.
// Calculate the next Fibonacci number
[n1, n2] = [n2, n1 + n2]; // Destructuring assignment!
}
}
You might think that calling fibonacci
would try to produce
an array of infinite length. But, it doesn’t. It only does enough work to prepare the next value in the sequence.
Notice that the yield
is inside of a while(true)
statement.
Generators pause their execution until the next time that for..of
asks
for another value.
Of course, if you try to iterate over fibonacci
with for..of
, it will
run forever unless you break
out of it. Or, your browser will simply
crash after a few seconds:
So, to recap: generator functions return iterators. But how do iterators work? What exactly is for..of
doing with an iterator to get those values out?
To answer that, consider the following generator:
function *roll() {
yield "Never gonna give you up";
yield "Never gonna let you down";
yield "Never gonna run around and desert you";
yield "Never gonna make you cry";
yield "Never gonna say goodbye";
yield "Never gonna tell a lie and hurt you";
}
As mentioned earlier, to get the iterator, simply call the generator.
let rick = roll();
To get the next value, invoke the next
function on the iterator,
which will cause it to yield its first value.
rick.next();
// {value: "Never gonna give you up", done: false}
Notice that the value returned is an object with two properties, value
and done
.
The value
property is what the iterator yield
ed. The done
property signals
whether or not there are more values.
If you continue to call rick.next()
, you’ll find that the iterator is eventually
drained of its values:
...
rick.next();
// {value: "Never gonna say goodbye", done: false}
rick.next();
// {value: "Never gonna tell a lie and hurt you", done: false}
rick.next();
// {value: undefined, done: true}
When an iterator has no more values to yield
, the value
property is undefined
, while the done
property is now true
.
Knowing how an iterator works, you could write a simple implementation of for..of
that uses callbacks:
function forOf(iter, callback) {
let result = iter.next();
while (!result.done) {
callback(result.value);
result = iter.next();
}
}
And that function could be used like so:
forOf(roll(), (val) => { console.log(val); });
Generators (things that produces iterators) and iterators
(things that can be iterated over) work based on two separate protocols: the iterable protocol and the
iterator protocol.
A generator function is really just syntactic sugar
for implementing the iterable protocol. This protocol
specifies that an object must have a method named
[Symbol.iterator]
. (That’s a method whose name
is the ES6 constant Symbol.iterator
.)
This [Symbol.iterator]
method can return any object, as long
as it is an iterator, meaning that it implements the iterator protocol.
The iterator protocol says that an iterator needs to have
a next
method, and that next
should return an
object with a value
property and a boolean done
property.
Here is an example of an object literal version of our
generator function.
let astley = {
[Symbol.iterator]() {
return this;
},
values: [
{
value: "Never gonna give you up",
done: false
},
{
value: "Never gonna let you down",
done: false
},
{
value: "Never gonna run around and desert you",
done: false
},
{
value: "Never gonna make you cry",
done: false
},
{
value: "Never gonna say goodbye",
done: false
},
{
value: "Never gonna tell a lie and hurt you",
done: false
},
{
value: undefined,
done: true
}
],
next() {
return this.values.shift();
}
};
The [Symbol.iterator]
method simply returns a reference to itself, since
it also implements the next
method. [Symbol.iterator]
is not required
to return this
— you are free to make your iterables separate from your
iterators. In this example, we made our iterable and iterator the same object.
You could use your iterable object like so:
for (let lalala of astley) {
console.log(lalala);
}
The for..of
construct implicitly calls astley[Symbol.iterator]
to get the iterator object. Then it calls the next
method over and over until the
value of the done
property is true
.
The object literal version is much more verbose than our generator function.
But, it is a good option if you already have an object that encapsulates some
sort of computed sequence and you want to use it with for..of
.
Generators were added to JavaScript in ES6. When combined with for..of
, they provide an easy way to create and consume sequences of values.
They have good support in evergreen browsers such as Chrome, Firefox and Edge. In older browsers
(e.g., IE < 9 and Safari < 9), you will want to transpile your code using a tool like Babel.
Stay tuned for upcoming posts about generators and iterators, in which we’ll look at error handling, recursion and coroutines.
In my previous post, we created a simple UI component with Facebook’s React library. Now, we’ll create data flows between multiple components.
We’re excited to announce that we’ve made our HTML5 Apps with jQuery bootcamp even better. Our new Cross-Platform JavaScript Apps with HTML5 class is...
Facebook's React is a JavaScript library for building high performance User Interfaces. As they like to say, it's the just "V" in MVC. Better...