2 min read

Making use of yield in JavaScript

A look into the new yield keyword in JavaScript, with a very basic Fibonacci sequence example.
Making use of yield in JavaScript

JavaScript's yield keyword is relatively new, and the more I use it the more I find it useful.

The point of yield is similar to using threads in other languages, in that it allows a function to stop execution until a later point in time.

A simple demonstration of that can be seen in using a simple generator function to count Fibonacci numbers starting at 0.

function* fibonacci() { 
  // Region A
  yield 0;

  // Region B
  yield 1;

  // Region C
  let secondMostRecent = 0;
  let mostRecent = 1;

  // cap out somewhere for demo
  while (mostRecent < 5) {
    // Region X
    const newValue = mostRecent + secondMostRecent;
    yield newValue;

    // Region Y
    secondMostRecent = mostRecent;
    mostRecent = newValue;
  }
  
  // Region D
  return mostRecent + secondMostRecent;
}

// Region 1
const test = fibonacci();
// Region 2
console.log(test.next());  // { value: 0, done: false }
// Region 3
console.log(test.next());  // { value: 1, done: false }
// Region 4
console.log(test.next());  // { value: 1, done: false }
// Region 5
console.log(test.next());  // { value: 2, done: false }
// Region 6
console.log(test.next());  // { value: 3, done: false }
// Region 7
console.log(test.next());  // { value: 5, done: false }
// Region 8
console.log(test.next());  // { value: 8, done: true }

// Region 9
const allValues = [...fibonacci()]; 
// [0, 1, 1, 2, 3, 5]; only the values yielded are destructured

To understand what regions of code run at what point, I've annotated with some comments.

When a generator function is run, it does not initially run any of its code. Instead, when you call a generator function, it returns a generator; no code is executed until you call the .next() method on that object.

When you call .next() on the generator, the body of the generator function executes as normal until the first yield keyword is used. Whatever value is passed to yield will then become the value property that is returned when you called .next() on the generator. The generator will then pause, and not execute any other lines until .next() is called again.

In our case, the following will be the order of our "regions":

  1. Region 1
  2. Region 2
  3. Region A
    4. Yields, returning { value: 0, done: false }
  4. Region 3
  5. Region B
    6. Yields, returning { value: 1, done: false }
  6. Region 4
  7. Region C
  8. Region X
    10. Yields, returning { value: 1, done: false }
  9. Region 5
  10. Region Y
  11. Region X
    14. Yields, returning { value: 2, done: false }
  12. Region 6
  13. Region Y
  14. Region X
    18. Yields, returning { value: 3, done: false }
  15. Region 7
  16. Region Y
  17. Region X
    22. Yields, returning { value: 5, done: false }
  18. Region 8
  19. Region Y
  20. Region D
    26. Returns, causing next to have { value: 8, done: true }
  21. Region 9
  22. (Repeat steps from for the destructure)

When we destructure, our second fibonacci() generator is created, and our program waits for the fibonacci generator to run through the code until our done status is true. Only the values from the yields, not the return, will be in the destructured array.