By using yield, generators can be suspended at any point in the control flow of your function, saving your current state of execution (scope & stack).
Without generators, this is more complicated:
- you need to explicitly keep track of the state
- branching and (especially) looping control structures need to be represented in a functional way, i.e. written recursively.
Generators are generically useful for traversing data structures, creating a simple stream-like iterator that yields all elements in order. Think of tree traversal, or DFS/BFS in graphs for simple examples.
function* traverseTree(node) { if (node == null) return; yield* traverseTree(node.left); yield node.value; yield* traverseTree(node.right); } // vs (not sure): function traverseTree(node) { var rl, l, r; return { next: function() { if (node == null && !r) return {done:true}; if (!l) l = traverseTree(node.left); if (!(rl=l.next()).done) return rl; if (node != null) { var n = {value:node.value}; node = null; r = traverseTree(node.right); return n; } return r.next(); } } }