Generator

Normally, functions run to completion, that is, once a function starts running, it finishes before anything else can interrupt.

function foo() {
  for (let i = 0; i < 10000; i++) {
    console.log(i)
  }
}

setTimeout(() => {
  console.log('Hello')
}, 1)

foo()

// `Hello` might be logged after 1 millisecond
  • A generator can pause itself in mid-execution, and can be resumed either right away or at a later time.
     
  • When it's paused, the generator can return a value, and the controlling code that resumes it can also send a value back in.

Declare

function *foo() { .. }
function* foo() { .. }
function * foo() { .. }
function*foo() { .. }

Execute

function *foo(x, y) {
	//..
}

foo(5, 10) // don't need to add asterisk when executing 

Behind the back

function *foo() {
	//..
}

let it = foo()
console.log(it)


// Object [Generator] {}
// to start/advance(推進) *foo(), call it.next()

Use yield to pause and return/receive value

function *foo() {
  for (let i = 0; i < 3; i++) {
    let x = yield i
    console.log(`x: ${x}`)
  }
}

let it = foo()

let a = it.next()
console.log(`a: ${a.value}`)

let b = it.next()
console.log(`b: ${b.value}`)

let c = it.next()
console.log(`c: ${c.value}`)

let d = it.next()
console.log(`d: ${d.value}`)

// a: 0
// x: undefined
// b: 1
// x: undefined
// c: 2
// x: undefined
// d: undefined

Use yield to pause and return/receive value

function *foo() {
  let x = 0
  for (let i = 1; i <= 3; i++) {
    x += yield i
    console.log(`x: ${x}`)
  }
}

let it = foo()

let a = it.next(100) // 第一次的 next(),參數沒有作用
console.log(`a: ${a.value}`)

let b = it.next(a.value)
console.log(`b: ${b.value}`)

let c = it.next(b.value)
console.log(`c: ${c.value}`)

let d = it.next(c.value)
console.log(`d: ${d.value}`)

// a: 1
// x: 1
// b: 2
// x: 3
// c: 3
// x: 6
// d: undefined

// 在 call it.next() 時, generator 會在 yield 的地方 pause
// 等到下一次的 it.next() 時,generator 才會從上次 yield 的地方把 next 傳入的參數 assign 並繼續往下跑
// 所以第一次的 next() 所傳的參數沒有用

Low precedence

let x = yield 3 + 2

// same as `let x = yield (3 + 2)`
// will yield 5, and assign x with any params passed in

let x = (yield 3) + 2

// will yield 3 first
// then assign x with any params passed in plus two

Yield delegation

function *foo() {
  yield *[1, 2, 3] // not the same as `yield [1,2,3]`
}

let it = foo()

console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())


// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: undefined, done: true }
  • yield * requires an iterable
  • Invokes that iterable's iterator, and delegates its own host generator's control to that iterator until it's exhausted.

Yield delegation

let arr1 = [1, 2, 3]

produce its iterator

( let it = arr1[Symbol.iterator( )  )

yield *

it.next()

whoever call

the Object[Generator].next( )

Yield delegation

That example has the same meaning as this one

function *foo() {
  yield 1
  yield 2
  yield 3
}

function *bar() {
  // bar delegates its control to the iterator(generator) of foo by calling foo()
  yield *foo()	
}

let it = bar()
console.log(it)

console.log(it.next())
console.log(it.next())
console.log(it.next())

Iterator control

function *foo () {
  // start up from initial paused state -> 3
  // but there's no yield waiting in the beginning, so 'first' will not be assigned to any variable -> 4

  // paused by the first yield and return 1 -> 5
  let x = yield 1
  // resumed by the second it.next() and assign 'second' to x -> 8

  // paused by the second yield and return 2  -> 9
  let y = yield 2
  // resumed by the third it.next() and assign 'third' to y -> 12
   
  // paused by the third yield and return 3 -> 13
  let z = yield 3
  // resumed by the fourth it.next() and assign 'fourth' to z -> 16
  
  // log output -> 17
  console.log(`x: ${x}, y: ${y}, z: ${x}`)
  
  // no more yield, return undefined -> 18
}

let it = foo()	// assign the generator object to it

// starting up the generator from its initial paused state -> 1
// pass 'first' as param -> 2
let first = it.next('first')
// get { value: 1, done: false } from first yield and assign to first -> 6

// pass 'second' as param -> 7
let second = it.next('second')
// get { value: 2, done: false } from second yield and assign to second -> 10

// pass 'third' as param -> 11
let third = it.next('third')
// get { value: 3, done: false } from third yield and assign to third -> 14

// pass 'fourth' as param -> 15
let fourth = it.next('fourth')
// get { value: undefined, done: true } and assign to third -> 14

Iterator control

function *foo() {
	yield *[1,2,3]
}

let it = foo()

for (let v of it) {
	console.log(v)
}

// 1
// 2
// 3

Normally, you don't get return from generator

function *foo() {
  let a = 10
  let b = 20
  return a + b
}

let it = foo()
console.log(it) // Object[Generator] {}

But you can do that with yield*

function *foo() {
  yield 1
  yield 2
  yield 3
  return 4
}

function *bar() {
  let x = yield *foo()
  console.log(`x: ${x}`)
  yield 5
}

// create the generator object for bar()
// but becuase of yield delegation, it will hand over the control to foo's iterator
let it = bar() 
// so, when you call bar.next(), it will implicitly call foo().next() until foo() is exhausted
 

console.log(it.next()) // { value: 1, done: false }
console.log(it.next()) // { value: 2, done: false }
console.log(it.next()) // { value: 3, done: false }

// since foo() is exhausted, it return the value to bar, and x is assigned
// log `x: 4`

console.log(it.next()) // { value: 5, done: false }
console.log(it.next()) // { value: undefined, done: true }

Early completion

function *foo() {
  yield 1
  yield 2
  yield 3
}

let it = foo()

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

console.log(it.return(42))	// { value: 42, done: true }

console.log(it.next())	// { value: undefined, done: true }

Early Abort

function* foo () {
  yield 1
  yield 2
  yield 3
}

let it = foo()

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

try {
  it.throw('Error!')
} catch(e) {
  console.log(e)	// Error!
}

console.log(it.next())	// { value: undefined, done: true }

Independent generators

function* foo () {
  yield 1;
  yield 2;
  yield 3;
}

let it1 = foo();
it1.next();			// { value: 1, done: false }
it1.next();			// { value: 2, done: false }

let it2 = foo();
it2.next();			// { value: 1, done: false }

it1.next();			// { value: 3, done: false }

it2.next();			// { value: 2, done: false }
it2.next();			// { value: 3, done: false }

it2.next();			// { value: undefined, done: true }
it1.next();			// { value: undefined, done: true }

Making generator if not using ES6

function foo () {
  // ..

  return {
    next: function (v) {
      // ..
    }

    // we'll skip `return(..)` and `throw(..)`
  }
}

Generator

By ianyshuang

Generator

Slide for You don't Know JS: Generator

  • 247