Since 2015, JavaScript has improved immensely.
It’s much more pleasant to use it now than ever.
In this article, we’ll look at JavaScript generators.
The First next()
The next
call starts an observer.
The only purpose of the first invocation next
is to start the observer.
So if we pass in a value to the first next
call, it won’t be obtained by yield
.
For example, we can write:
function* genFn() {
while (true) {
console.log(yield);
}
}
const gen = genFn();
gen.next('a');
gen.next('b');
gen.next('c');
Then:
b
c
is logged.
The first call of next
feeds 'a'
to the generator, but there’s no way to receive it since there’s no yield
statement.
Once it runs yield
, then the value can be received.
yield
‘s operand is returned once next
is called.
The returned value would be undefined
since we don’t have an operand to go with it.
The 2nd invocation of next
feeds 'b'
into next
, which is received by yield
.
And that’s logged with the console log.
And next
returned undefined
value again because yield
has no operand.
Therefore, we can only feed data to yield
when next
is called.
For example, we can write:
function* genFn() {
while (true) {
console.log(yield);
}
}
const gen = genFn();
gen.next();
gen.next('a');
gen.next('b');
gen.next('c');
Then we get:
a
b
c
logged in the console.
yield
Binds Loosely
yield
treats the whole expression that’s after it as its operand.
For instance, if we have:
yield foo + bar + baz;
Then it’s treated as:
yield (foo + bar + baz);
rather than:
(yield foo) + bar + baz;
Therefore, we’ve to wrap our yield
expressions with parentheses so that we can avoid syntax errors.
For example, instead of writing:
function* genFn() {
console.log('yielded: ' + yield);
}
We write:
function* genFn() {
console.log('yielded: ' + (yield));
}
return()
and throw()
The return
and throw
methods are similar to next
.
return
lets us return something at the location of yield
.
throw
lets us throw an expression at the location of yield
.
The next
method is suspended at a yield
operator.
The value from next
is sent to yield
.
return
terminates the generator.
For example, we can write:
function* genFn() {
try {
console.log('start');
yield;
} finally {
console.log('end');
}
}
const gen = genFn();
gen.next()
gen.return('finished')
We have the genFn
function that returns a generator with a given value.
The next
method will run the generator.
And return
ends the generator.
If we log the value of the return
call, we get:
{value: "finished", done: true}
throw() lets us Throw Errors
We can throw errors with the throw
keyword.
For instance, we can write:
function* genFn() {
try {
console.log('started');
yield;
} catch (error) {
console.log(error);
}
}
const gen = genFn();
gen.next();
console.log(gen.throw(new Error('error')));
We call throw
with the generator function.
The catch
block will be invoked once we call next
to start the generator.
Then we’ll see:
Error: error
logged with the console log of the catch
block.
And {value: undefined, done: true}
is returned from the throw
call.
Conclusion
We can call the next
method and use it with the yield
operator without an operand.
This will take the value from next
and return it.
Generators also have the return
and throw
methods to end the generator and throw errors.