TL;DR

You can override the result returned from Generator.prototype.return(). :O

next

Generators create iterators. You can step through them with next.

function* generator() {
  yield 1
  yield 2
  yield 3
  return 'banana'
}

const iterator = generator()
console.log(iterator.next())
// { value: 1, done: false }

console.log(iterator.next())
// { value: 2, done: false }

console.log(iterator.next())
// { value: 3, done: false }

console.log(iterator.next())
// { value: 'banana', done: true }

throw

You can also throw.

function* generator() {
  yield 1
  yield 2
  yield 3
  return 'banana'
}

const iterator = generator()
console.log(iterator.next())
// { value: 1, done: false }

console.log(iterator.throw(new Error('dang')))
// Error: dang

As expected, you can catch the errors.

function* generator() {
  try {
    yield 1
    yield 2
    yield 3
  } catch (e) {
    console.log('Oh, no!')
  } finally {
    return 'banana'
  }
}

const iterator = generator()

console.log(iterator.next())
// { value: 1, done: false }

console.log(iterator.throw(new Error('dang')))
// Oh, no!
// { value: 'banana', done: true }

return

You can skip all the yields and make the generator return a value of your choosing.

function* generator() {
  yield 1
  yield 2
  yield 3
  return 'banana'
}

const iterator = generator()

console.log(iterator.next())
// { value: 1, done: false }

console.log(iterator.return(777))
// { value: 777, done: true }

But…

What happens if you have a finally block and return? Finally statements

execute regardless of whether an exception was thrown or caught.

Hrm…

function* generator() {
  try {
    yield 1
    yield 2
    yield 3
  } finally {
    return 'I return whatever I want.'
  }
}

const iterator = generator()

console.log(iterator.next())
// { value: 1, done: false }

console.log(iterator.return('Thou shalt return 42!'))
// { value: 'I return whatever I want.', done: true }