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
- perform
foo
The function returns oneTraverser object
To call the traverser objectnext
Method to move the pointer to the next state - Each call
next
Method, the inner pointer executes from where the function header was last stopped until the next yield statement or return is encountered - about
next
Method 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 statement
next
Method, 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:
- The yield statement cannot be used in normal functions, otherwise an error will be reported
- 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
- use
for... of
Loop, no need to usenext
statements - Once the next method returns the object’s
done
Properties fortrue
.for... of
The loop will stop for... of
Object properties are not returned after the loop terminatesdone
为true
So 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 theta
for... of
The syntactic sugar - It could have been
for... of
alternative
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 has
Iterator
The 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 function
loopTree
, which takes an array or a number as an argument - If the argument is an array, loop through the array and use
yield*
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:
Generator
The execution of a function must depend on the executor, andasync
Functions come with their own actuators, which means,async
Functions are executed just like normal functions on one line- You don’t need to call
next
Method, automatically executed async
It means that there are asynchronous operations in the function,await
Indicates that the following expression needs to wait for the result- To achieve synchronous execution of asynchronous operations,
await
The command must be followed byPromise
Object, 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