JavaScript Generators Tutorial - Section 2: Fundamentals
The yield* expression delegates to another generator or iterable, yielding all its values.
function* inner() {
yield 1;
yield 2;
}
function* outer() {
yield 'start';
yield* inner(); // Delegate to inner generator
yield 'end';
}
console.log([...outer()]); // ['start', 1, 2, 'end']
// Without yield* - yields the generator object
function* withoutDelegation() {
yield inner(); // Yields generator object
}
console.log([...withoutDelegation()]);
// [Object [Generator] {}]
// With yield* - yields all values
function* withDelegation() {
yield* inner(); // Yields 1, then 2
}
console.log([...withDelegation()]); // [1, 2]
function* arrayDelegation() {
yield* [1, 2, 3];
yield* 'hello';
yield* new Set([4, 5, 6]);
}
console.log([...arrayDelegation()]);
// [1, 2, 3, 'h', 'e', 'l', 'l', 'o', 4, 5, 6]
function* numbers() {
yield 1;
yield 2;
yield 3;
}
function* letters() {
yield 'a';
yield 'b';
yield 'c';
}
function* combined() {
yield* numbers();
yield* letters();
}
console.log([...combined()]); // [1, 2, 3, 'a', 'b', 'c']
function* flatten(array) {
for (const item of array) {
if (Array.isArray(item)) {
yield* flatten(item); // Recursive delegation
} else {
yield item;
}
}
}
const nested = [1, [2, [3, [4]], 5], 6];
console.log([...flatten(nested)]); // [1, 2, 3, 4, 5, 6]
class TreeNode {
constructor(value, children = []) {
this.value = value;
this.children = children;
}
*traverse() {
yield this.value;
for (const child of this.children) {
yield* child.traverse(); // Delegate to child
}
}
}
const tree = new TreeNode(1, [
new TreeNode(2, [
new TreeNode(4),
new TreeNode(5)
]),
new TreeNode(3, [
new TreeNode(6)
])
]);
console.log([...tree.traverse()]); // [1, 2, 4, 5, 3, 6]
function* inner() {
yield 1;
yield 2;
return 'inner done';
}
function* outer() {
const result = yield* inner();
console.log('Inner returned:', result);
yield 3;
}
console.log([...outer()]);
// Logs: Inner returned: inner done
// Returns: [1, 2, 3]
class Directory {
constructor(name, items = []) {
this.name = name;
this.items = items;
}
*walk() {
yield this.name;
for (const item of this.items) {
if (item instanceof Directory) {
yield* item.walk(); // Recurse into subdirectories
} else {
yield item;
}
}
}
}
const root = new Directory('root', [
'file1.txt',
new Directory('subdir1', [
'file2.txt',
'file3.txt'
]),
new Directory('subdir2', [
'file4.txt'
])
]);
for (const item of root.walk()) {
console.log(item);
}
// root
// file1.txt
// subdir1
// file2.txt
// file3.txt
// subdir2
// file4.txt
function* source() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
function* filter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) {
yield item;
}
}
}
function* map(iterable, transform) {
for (const item of iterable) {
yield transform(item);
}
}
function* pipeline() {
yield* map(
filter(source(), x => x % 2 === 0),
x => x * 2
);
}
console.log([...pipeline()]); // [4, 8]
function* compose(...generators) {
for (const gen of generators) {
yield* gen;
}
}
function* gen1() { yield 1; yield 2; }
function* gen2() { yield 3; yield 4; }
function* gen3() { yield 5; yield 6; }
const combined = compose(gen1(), gen2(), gen3());
console.log([...combined]); // [1, 2, 3, 4, 5, 6]
yield* delegates to another iterableNow let's explore two-way communication patterns in depth!