This article is first published CSDN
The handling of asynchronous operations introduces promises and generators. Promise is known to solve the infamous callback hell problem to some extent. But the syntax of using the Promise chain-call when handling multiple asynchronous operations is also less elegant and intuitive. Generators take Promise one step further and allow us to describe our asynchronous processes synchronously.
Basic Principles of Generator
Generator a generator function is a special function in ES6 that is declared by function*. The function body uses yield to indicate the pause point of the function. The function returns an iterator, and the function is paused until the yield statement. The yield statement is then executed by calling the returned iterator next() method.
function* generator() {
yield 1;
yield 2;
yield 3
}
var gen = generator()
Copy the code
Generator functions are generators in ES6, which in turn generate iterators and interrupt code execution without finishing all at once, so that we can describe our asynchronous processes synchronously.
When the generator function is called, it does not execute and returns not the result of the function’s execution, but a pointer to an internal state that can be moved to the next state by calling the next method.
console.log(gen.next()) //{ value: 1, done: false }
console.log(gen.next()) //{ value: 2, done: false }
console.log(gen.next()) //{ value: 3, done: false }
console.log(gen.next()) //{ value: undefined, done: true }
Copy the code
The Generator can instantiate an iterator, and the yield statement is used to interrupt the execution of the code; that is, with the next() method, only one yield statement is executed at a time.
Insert a point about yield
Yield can be followed by any valid JavaScript expression, and the yield statement can occur in places equivalent to where a regular assignment expression (such as a=3) can occur.
b = 2 + a = 3 / / is illegal
b = 2 + (a = 3) / / legal
b = 2 + yield 3 / / is illegal
b = 2 + (yield 3) / / legal
Copy the code
The yield keyword of the copy code has a low priority, and almost any expression after yield is evaluated before yielding a value to the outside world. And yield is the right associative operator, which means that yield yield 123 is equivalent to (yield (yield 123)).
Generator object methods
- The return method. Like the return method of the iterator interface, it is used when a generator function encounters an exception or aborts prematurely (as in the for… Automatically called when the of loop breaks ahead of completion), and the generator object becomes terminated and can no longer generate values. It can also be called manually to terminate the iterator. If you pass a parameter in the return call, that parameter will be used as the value property value of the object eventually returned.
function* generator() {
yield 1
try {
yield 2
} finally {
yield 3}}let genl = generator()
console.log(genl.next()) //{ value: 1, done: false }
console.log(genl.next()) //{ value: 2, done: false }
console.log(genl.return(4)) //{ value: 3, done: false }
console.log(genl.next()) //{ value: 4, done: true }
let gen =generator()
console.log(gen.next()) //{ value: 1, done: false }
console.log(gen.return(4)) //{ value: 4, done: true }
console.log(gen.next()) //{ value: undefined, done: true }
console.log(gen.next()) //{ value: undefined, done: true }
Copy the code
Return terminates the entire Generator, in other words: yield written after return does not execute. Its ability to interrupt code execution helps control the order in which asynchronous code is executed
- Throw method. Calling this method throws an error at the point where execution of the generator function is currently paused. If the error is not caught in the generator function, the generator object state terminates and the error propagates globally from within the current throw method. When the next method is called to execute a generator function, if an error is thrown inside the generator function and not caught, it is propagated globally from within the next method
Analysis of the implementation
Analysis of the following code:
function* generator() {
let result = yield 'hello'
console.log(result)
}
var gen = generator()
// console.log(gen.next()) // { value: 1, done: false }
console.log(gen.next(22)) //{ value: 1, done: false }
console.log(gen.next(2)) //2 { value: undefined, done: true }
Copy the code
The arguments passed in the first call to the next method are not available internally to the generator, or have no practical meaning, because the generator function has not yet been executed. The first call to the Next method is used to start the generator function.
const fs = require("fs").promises;
/ / generator
function * read(){
yield fs.readFile("./name.txt"."utf-8")}/ / the iterator
let it = read()
// console.log(it.next()) // { value: Promise {
}, done: false }
it.next().value.then(data= >{
console.log(data) // name
})
Copy the code
This is a simple generator instance
Start deform file: name.txt: age.txt age.txt: 666
const fs = require("fs").promises;
function * read(){
let concent = yield fs.readFile("./name.txt"."utf-8")
let age = yield fs.readFile(concent,"utf-8")
return age
}
let it = read()
it.next().value.then(data= >{
it.next(data).value.then(data= >{
let r = it.next(data)
console.log(r) // { value: '666', done: true }})})Copy the code
Only two so much trouble, fortunately TJ’s CO library download
npm install co
Copy the code
Co is a secondary wrapper library of Generator implemented by TJ
const fs = require("fs").promises;
function * read(){
let concent = yield fs.readFile("./name.txt"."utf-8")
let age = yield fs.readFile(concent,"utf-8")
return age
}
let co = require("co")
co(read()).then(data= >{
console.log(data) / / 666
})
Copy the code
Generator async/await syntax is very useful and the code is more concise!
const fs = require("fs").promises;
async function read() {
let concent = await fs.readFile("./name.txt"."utf-8")
let age = await fs.readFile(concent, "utf-8")
return age
}
read().then(data= > {
console.log(data) / / 666
})
Copy the code
Async/await syntactic sugar
The async/await syntax introduced in ES7 is the syntactic sugar of Generator functions, except that the former no longer requires an executor. Executing an async function directly automatically executes the logic inside the function. The result of the async function execution returns a Promise object, whose state changes depending on the state of the Promise after the await statement in the async function and the final return value of the async function. Next I’ll focus on error handling in async functions.
The await keyword can be followed by a Promise object or a primitive type value. If it is a Promise object, the completion value of the Promise object is returned as the await statement. Once the Promise object is converted to the Rejected state, the Promise object returned by the async function will also be converted to the Rejected state
async function aa() {await Promise.reject('error! ')}
aa().then((a)= > console.log('resolved'), e => console.error(e)) // error!
Copy the code
If the Promise object after await is converted to Rejected, inside the async function you can call a try… Catch catches the corresponding error.
async function as() {
try {
await Promise.reject('error! ')}catch(e) {
console.log(e)
}
}
as().then((a)= > console.log('resolved'), e => console.error(e))
// error!
// resolved
Copy the code
If the async function does not capture the Promise with the Rejected state, then the aa function will not catch an error. Instead, the Promise returned by the AA function will be changed to the Rejected state
conclusion
From the callback function at the beginning, to the Promise proposed by the community leaders and later added to ES6, to the generation iteration of generator, to TJ’s CO library, although the code in JavaScript is single threaded, the solution of asynchronous problems is getting stronger and stronger. The syntactic sugar of generator async and await combined with promise is now the mainstream and effective way.