What is a generator function?

Generator -> producer yield -> output

Generator functions are an asynchronous programming solution provided by ES6

Executing the Generator returns an iterator object, which means that we can use the next method to iterate over each state within the Generator

Since a generator function has multiple states inside it, there should always be an identifier to determine where the function should stop during traversal, so we need yield


Yield statements

Here is a simple example to explain in detail how Yield works

function* foo() {
    yield 'hello'
    
    console.log('come from second yield')
    yield 'world'
    
    return 'ending'
}

const g = foo()

// Execute process
> g.next()
< { value: 'hello'.done: false }

> g.next()
  log: come from second yieldThe < {value: 'world'.done: false }

> g.next()
< { value: 'ending'.done: true }

> g.next()
< { value: undefined.done: true }
Copy the code
  • performfooThe function returns oneTraverser objectTo call the traverser objectnextMethod to move the pointer to the next state
  • Each callnextMethod, the inner pointer executes from where the function header was last stopped until the next yield statement or return is encountered
  • aboutnextMethod return object:
    • Value: The value immediately following yield or return, or the result of an expression
    • Done: Indicates whether the traversal is finished, Boolean type
  • If the internal state encounters a return statement, the traversal ends directly, that is, it is called again with or without an expression or yield statementnextMethod, will only return{ value: undefined, done: true }


As you can see from the behavior of the Generator, it is essentially equivalent to javascript providing a syntax capability for manual “lazy evaluation”


Matters needing attention:

  1. The yield statement cannot be used in normal functions, otherwise an error will be reported
  2. If the yield statement is in an expression, it must be inside parentheses.console.log('hello' + (yiled 123))

The generator function and the next method pass parameters

Generator functions can pass parameters, but there are two ways for generator functions to pass parameters

The generator function passes parameters

A parameter can be read in any state of a generator function in the same way as a normal function

function* foo(x) {
    console.log(x)
    yield 'step 1'
    
    console.log(x)
    yield 'step 2'
    
    console.log(x)
    return 'step 3'
}

const g = foo('hello world')
Copy the code

The code snippet above reads the hello World argument in any state of the function body

The next method passes parameters

The next method passes parameters in a completely different way from normal functions

The yield statement itself returns no value, or always returns undefined. The next method can take an argument that is treated as the return value of all yield statements up to that state

The yield statement returns no value?

Let’s look at the following expression first

function* foo() {
  const x = yield 10

  console.log(x)
  return 'ending'
}

const g = foo()
Copy the code


g.next() log: undefined < { value: ‘ending’, done: true }

<br> If we want to print 'x' with a value of 'hello world', we must use the next method to pass the argument, which will be treated as the yield return javascript > g.ext () < {value: 10.done: false }

> g.next('hello world')
  log: hello world
< { value: 'ending'.done: true }
Copy the code

exercises

Calculation with generator

function* foo(x) {
    let y = 2 * (yield (x + 5))
    let z = yield y / 4 + 3
    return (x + y - z)
}

const g = foo(10)
Copy the code
g.next()  / / 1
g.next(4) / / 2
g.next(8) / / 3
Copy the code

Operation process:

1.
x = 10
yield (10 + 5) = >15
> { value: 15.done: false }

2.
y = 2 * 4= >8
yield (8 / 4 + 3) = >5
> {value: 5.done: false}

3.
x = 10
y = 8 // Retains the value from the last next method execution
z = 8
return (10 + 8 - 8) = > 10
> { value: 10.done: true }
Copy the code

Generator internal error capture

  • Generator functions can catch errors inside the function body
  • Once an error is caught, the Generator stops traversing,done = true
function* foo() {
  try {    
    yield console.log(variate)
    yield console.log('hello world')}catch(err) {
    console.log(err)    
  }
}

const g = foo()

g.next()

> ReferenceError: variate is not defined
    at foo (index.js:3)
    at foo.next (<anonymous>)
    at <anonymous>:1:3
> { value: undefined, done: true }
Copy the code

If an error is thrown in an internal catch fragment using the global method throw, the error can still be thrown by an external try… Catch:

function* foo() {
  try {    
    yield console.log(variate)
    yield console.log('hello world')}catch(err) {
    throw err 
  }
}

const g = foo()

try {
    g.next()
} catch(err) {
    console.log('External capture', err)} > External captureReferenceError: variate is not defined
    at foo (index.js:3)
    at foo.next (<anonymous>)
    at index.js:13
Copy the code

for… Of circulation

Because the generator returns an traverser object, we can use for… The of loop to iterate over it

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

const g = foo()

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

/ / < 1
/ / < 2
/ / < 3
Copy the code
  • usefor... ofLoop, no need to usenextstatements
  • Once the next method returns the object’sdoneProperties fortrue.for... ofThe loop will stop
  • for... ofObject properties are not returned after the loop terminatesdonetrueSo the above example does not return the ending value in return

Yield * statement

This statement is used to call another generator function within a generator function

function* bar() {
    yield 3
    yield 4
}

function* foo() {
    yield 1
    yield 2
    yield* bar()
    yield 5
    return 'ending'
}

for (let v of foo()) {
    console.log(v)
}
// < 1 2 3 4 5
Copy the code

yield* Truth:

  • This statement actually completes the loop over the traverser object
  • So it can be viewed as thetafor... ofThe syntactic sugar
  • It could have beenfor... ofalternative
Yield * bar() # equivalent to: for (let v of bar()) {yield v}Copy the code
  • It can even iterate over groups of numbers:
function* gen() {
    yield* [1.2.3]
    yield* 'abc'
}

for (let v of gen()) {
    console.log(v)
}
// < 1 2 3 a b c
Copy the code
  • That is, anything that hasIteratorThe data structure of the interface can beyield*traverse

yield* The return value can be saved

Unlike yield (which itself does not return a value and must be assigned by the next method), if a generator propped by yield* has a return statement, the value returned by return can be stored permanently

function* foo() {
  yield 2
  return 'hello yield*'
}

function* gen() {
  const a = yield 1

  console.log(a) // -> undefined
  const b = yield* foo()

  console.log(b) // -> hello yield*
  yield 2

  console.log(b) // -> hello yield*
  yield 3
}

const g = gen()

Copy the code

useyield* Fetches all members of the nested array

const tree = [1.2[3.4[5.6[7.8.9]]].10.11]

function* loopTree(tree) {
  if (Array.isArray(tree)) {
    for (let i = 0; i < tree.length; i ++) {
      yield* loopTree(tree[i])
    }
  } else {
    yield tree
  }
}

for (let v of loopTree(tree)) {
  console.log(v)
}
Copy the code
  • Creating a generator functionloopTree, which takes an array or a number as an argument
  • If the argument is an array, loop through the array and useyield*Calls itself
  • If it is not an array, the value is returned

Practical use of generator functions

Asynchronous operations are expressed synchronously

/** * Ordinary XHR request encapsulation * @param {String} URL request address * @return {void} void */
function call(url) {
  const xhr = new XMLHttpRequest()
  xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
        const res = JSON.parse(xhr.responseText)

        // 3. The request is successful, and the request result is assigned to the result variable and goes to the next state
        g.next(res)
      } else {
        console.log(`error: ${xhr.status}`)
      }
    }
  }
  xhr.open('get', url, true)
  xhr.send(null)}function* fetchData() {
  // 2. Send the XHR request
  const result = yield call('https://www.vue-js.com/api/v1/topics') 
  
  // 4. Print the request result
  console.log(result)
}

const g = fetchData()

// 1. Start traversing the generator function
g.next()
Copy the code

Deploy the Iterator interface

const obj = {
  name: 'Muzi qi'.age: '25'.city: 'chongqing'
}

/** * Deploy the Iterator interface * @param {Object} obj Object * @yield {Array} convert Object properties to arrays */
function* iterEntires(obj) {
  let keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i ++) {
    let key = keys[i]
    yield [key, obj[key]]
  }
}

for (let [key, value] of iterEntires(obj)) {
  console.log(key, value)} < name muziqi < age25"The city of chongqingCopy the code

Combined with Promise

When combined with a Promise, a generator is essentially an encapsulated implementation of async/await, which is described in detail in the following sections

The principle of

// 1. Define generator functions
// - The native fetch method it returns is a Promise object
function* fetchTopics() {
  yield fetch('https://www.vue-js.com/api/v1/topics')}const g = fetchTopics()

// 2. Call next
const result = g.next()

// 3.g.ext () returns {value:... The value in, done} is the Promise object
result.value
  .then(res= > res.json())
  .then(res= > console.log(res))

Copy the code

Encapsulated method implementation

/** * encapsulates the method used to execute generator functions * @param {Func} generator generator functions */
function fork(generator) {
  // 1. Pass in the generator function and execute to return a traverser object
  const it = generator()

  /** * 3. Iterate over all the Promise states in generator functions * go calls itself repeatedly using the next method, * @param {Object} result Returns data */ after executing the next method
  function go(result) {
    if (result.done) return result.value

    return result.value.then(
      value= > go(it.next(value)),
      error => go(it.throw(error))
    )
  }

  // 2. Execute the next statement for the first time to enter the go function logic
  go(it.next())
}

/** * Normal Promise request method * @param {String} URL request path */
function call(url) {
  return new Promise(resolve= > {
    fetch(url)
      .then(res= > res.json())
      .then(res= > resolve(res))
  })
}

/** * Business logic generator function * - request Topics to get a list of all topics * - Request details of the first topic */ via the ID returned by Topics
const fetchTopics = function* () {
  try {
    const topic = yield call('https://www.vue-js.com/api/v1/topics')

    const id = topic.data[0].id
    const detail = yield call(`https://www.vue-js.com/api/v1/topic/${id}`)

    console.log(topic, detail)
  } catch(error) {
    console.log(error)
  }
}

fork(fetchTopics)
Copy the code

Async function

Async functions are ES7 syntax and need to be transcoded by Babel or Regenerator

Async functions are syntactic sugar for Generator functions and have the following characteristics:

  • GeneratorThe execution of a function must depend on the executor, andasyncFunctions come with their own actuators, which means,asyncFunctions are executed just like normal functions on one line
  • You don’t need to callnextMethod, automatically executed
  • asyncIt means that there are asynchronous operations in the function,awaitIndicates that the following expression needs to wait for the result
  • To achieve synchronous execution of asynchronous operations,awaitThe command must be followed byPromiseObject, if of another primitive type, is equivalent to a synchronous operation
function timeout() {
  return new Promise(resolve= > {
    setTimeout(resolve, 2000)})}async function go() {
  await timeout().then((a)= > console.log(1))
  console.log(2)
}

go()

// Execute the output, first 1 and then 2
/ / - > 1
/ / - > 2
Copy the code
  • Async returns a Promise and Generator returns an Iterator, so we can use then to specify what to do next
function timeout() {
  return new Promise(resolve= > {
    setTimeout(resolve, 2000)})}async function go() {
  await timeout().then((a)= > console.log(1))
  console.log(2)
}

go().then((a)= > console.log(3))
/ / - > 1
/ / - > 2
/ / - > 3
Copy the code
  • It can be said that an async function can be viewed as a Promise wrapped in asynchronous operations, and an await command is a syntactic sugar of an internal THEN command

Let’s use the async function to implement the previous asynchronous request example

const call = url= > (
  new Promise(resolve= > {
    fetch(url)
      .then(res= > res.json())
      .then(res= > resolve(res))
  })
)

const fetchTopics = async function() {
  const topic = await call('https://www.vue-js.com/api/v1/topics')

  const id = topic.data[0].id
  const detail = await call(`https://www.vue-js.com/api/v1/topic/${id}`)

  console.log(topic, detail)
}

fetchTopics().then((a)= > console.log('request success! '))
Copy the code

Conclusion: The implementation of async function is much simpler than Generator function, with almost no semantically irrelevant code. It provides the automatic executor in Generator writing at the language level instead of exposing it to users, thus reducing a lot of code