Generator function
Generator
Functions look like pointer functions, but in fact they are similar to our ordinary functions, and they have two characteristics
1、function
There is an asterisk between the keyword and the function name
2. Use internal function bodyyield
Expressions that define different internal states (yield
In English, it means "output"), and here it means pause and waiting for execution.
Generator
Functions run like normal functions, but encounteryield
Keywordspause
, need to wait for the callnext
Only aftercontinue
Execute later until the next one is encounteredyield
orreturn
yield
Asexpression
When using one, you need to use it()
Include it, otherwise an error will be reported, for example:2 * (yield x)
next
Signedgenerator
The function starts to execute the statement later, and the default execution isNext yield
statement and returnContent after yield
arrivevalue
No encountersreturn
orFunction end
,done
The parameters arefalse
,otherwisedone
The parameters aretrue
return
It indicates the end of the function, and this is no exception, but the content returned at the end can also benext
Return; otherwise, do not writereturn
Then withreturn undefined
similar
You can refer to the following cases to understand
function* generator() { yield 1 yield 2 yield 3 yield 4 return 5 // { value: 5, done: true }, and then next will be the same as not writing // If you don't write return, it will be the same as return undefined. Return { value: undefined, done: true }} //Get generator traverserlet g = generator() let next = null do { next = () //Start and execute to the next yield statement and return (next) }while(next?.done === false) //Execute until return or function ends and stop printingnext = () (next) // { value: 1, done: false } // { value: 2, done: false } // { value: 3, done: false } // { value: 4, done: false } // havereturn 5 Print { value: 5, done: true }, 没have则Print { value: undefined, done: true }
Ifclass
How to represent it in ? It contains the constructor package. The writing method is different. You only need to add one in front.*
Just
class A { // Just add * in front * generator() { yield 1 yield 2 } } let a = new A() let g = () let next = () (next) next = () (next)
generator and Iterator traverser
You can see from the print above,Generator function
, actually returns aIterator traverser
, so we can also execute it through the means of a traverserGenerator traverser
Use belowfor ... of
Let's go through our aboveGenerator traverser
// From the above, we can see that the generator function returns an Iterator traverser object// Let's try it throughlet g = generator() for (let item of g) { (item) } //Results Print 1 2 3 4
It turns out that the last one is not printedreturn
, you will know that the traverser encountersdone
fortrue
It will end directly (if used as a traverser, do notreturn statement
Put the content through)
Let's compare and see oursGenerator traverser
Is it reallyIteraotr traverser
,DiscoverExactly the same
//g[]() === g //The traverser attributes are consistent,Return as true,Don't believe in the printing traverser attribute comparison
next
As mentioned earlier,Generator traverser
Callnext()
, it will continue to execute to the nextyield statement
also,next
CanTransfer the ginseng
,usPassed parameters
Will beThe result returned by the previous yield
, no arguments are passed, the default return isundefined
Notice: yield
Returned resultsno
When yield is used as an expression, it needs to be enclosed with () or an error will be reported
Let’s see the results after looking at the case below (the process has been marked)
//When including expressions, the yield statement needs to be enclosed in brackets, otherwise an error will be reportedfunction* generator() { let num1 = yield 1 ('num1', num1) let num2 = num1 * (yield 2) ('num2', num2) yield 3 yield 4 } //The value passed in next is used as the return value of the previous yieldlet g = generator() let next = () //Execute to yield 1 at this time, and wrap the result of the execution of yield 1(next) //{ value: 1, done: false } //Pause 2, as the return value of yield 1 of the previous statement (the received values are undefined)//That is, return 2 and assign it to num, then execute to (yield 2), and wrap the result returned (yield 2)next = (2) //After execution is completed, print: num1 2(next) //{ value: 2, done: false } //Passed in 3. As the return value of (yield 2) of the previous statement (the received values are undefined)//That is: num2 = num1 * 3, then execute to yield 3, and wrap the result returned by yield 3next = (3) //After execution is completed, print: num2 6(next) //{ value: 3, done: false }
From the above, you can seenext parameter transfer
It's very useful. In addition to the above, you can even use the passed parameters to break out of the dead loop...
throw
Herethrow
For passinggenerator
The exception thrown by the traverser will be thrown toInside generator function
, so you need to pay attention to throwingThe location of the exception
to better cooperatetry...catch
use
//throw() // It is equivalent to throwing an exception in the generatorfunction* generator() { try { yield 1 }catch(err) { (err) } yield 2 yield 3 yield 4 } let g = generator() let next = () (next) //After executing yield 1, an exception is thrown between yield 1 ~ yield 2. Therefore, try...catch package main yield 1 onlylet t = ('Lalala') // Throw an exception and execute it to the next yield. If no error is processed, the program exception will be performed.(t) next = () (next)
return
Designatedreturn
Airi ends immediatelygenerator function
, and return{ value: undefined, done: true }
, the above introduction
function* generator() { yield 1 yield 2 yield 3 yield 4 } let g = generator() next = () next = () //Return directly { value: undefined, done: true }, if passed parameter, return the parameter passed by return(next) //Will end directly
In addition, when there istry...finally
When the code block is executedtry
When insidereturn
,meetingStill executed finally
The statement inside will be executedFinally inside
ofyield
Statement,finally
The execution is the real end, andreturn
The parameters brought in will be applied tofinally
Statementend
function* generator() { yield 1 try { //If you execute it here, if you return outside, you will need to finish finally yield 2 yield 3 }finally { yield 7 yield 8 } yield 4 } let g1 = generator() next = () // { value: 1, done: false } next = () // { value: 2, done: false } next = (100) // { value: 7, done: false }, return after return, return the next one, which is finally yield 7, here to return a value and test the last return valuenext = () // { value: 8, done: false } next = () // { value: 100, done: true } next = () // { value: undefined, done: true }
yield*
yield*
Looks like a pointer, pointing to aGenerator traverser
, it will automatically expand, without saying much
//yield* will expand the generator functionfunction* gen1() { yield 1 yield 2 } function* gen2() { yield 0 yield* gen1() } //Equivalent to the statements expanded below, it can be seen that the yield statements are always executed one by one, and even if they are nested, they will not execute a bunch of them at a time.function* gen3() { yield 0 yield 1 yield 2 }
Generator Application
generator
There are many applications, here are two of them, which are a test
Switch switches normally and requires status. Here, the generator traverser is used to avoid adding new switch states (not suitable for multi-party control, for example: the state needs to be remotely synchronized with the background)
//Switch statusfunction* toggleSwitch() { while(true) { ('On the light') yield 1 ('Down the light') yield 0 } } //Is it easy to switchlet t = toggleSwitch() let next = () //Open the light(next) next = () // Turn off the light(next)
Process management, external management does not need to care about internal operations (for example: processing software, different types of operations are required to click next after completion)
function* order() { yield 'Order received' yield 'Processing' yield 'Send to inspection' yield 'Quality Inspection' yield 'Shipping' yield 'Complete Order' } let o = order() let next = () //Received the ordernext = () //Processingnext = () //Send to inspectionnext = () //Quality inspectionnext = () //Shippingnext = () //Complete the order
async, await function
async、await
We usually use it more, so let me briefly introduce it here
async、await
It's based onGenerator function
Renovated, it has been improvedGenerator function
As a common function pain point, it has been addedActuator
, can be executed directly like ordinary functions, and even moreconvenient
,andSemantics are clearer
, the result returns onePromise
(It can be seen that it is packaged using Promise)
in short:async
Declare an asynchronous function,await
Wait for execution whenawait
Only after the statement is executed will the following statement be executed (the error is thrown directly and the error is not executed later) and if there is noawait
The existence ofasync
Functions are no different from the execution order of an ordinary synchronous function.
ps: aboutpromise
An introduction before,await
The execution process is the same as it is. In fact, the next task in the task queue will be executed immediately. Only after the previous task is executed, the statement after await will be executed.
Make a simple use case,
async function a() { await promise1... await promise2... // If you do not return your own content, the returned one is undefined return 1 } function cc() { a().then(res => { (res) }).catch(err => { (err) }) }
You may wonder, what if the first promise goes wrong, will the second one be executed?
Answer: No, after the first error occurs, an exception will be thrown, and the execution of the interpreter will be terminated and the error will be directly reported to the outside world.
The following is to improve the previous case
function a1() { return ('What went wrong') } function a2() { return ('Successfully') } async function a() { await a1() (11111111111) await a2() // If you do not return your own content, the returned one is undefined return 1 } function cc() { a().then(res => { (res) }).catch(err => { (err) }) } //As a result, no printing was performed 11111111111,And it has been executed .catch middle
Below we simulate the case of passing multiple pictures, we can see thatasync、await
How convenient to apply
function uploadAImage(fileUrl) { return new Promise(function(resolve, reject) { setTimeout(() => { let res = fileUrl + 'Successfully' (res) resolve(res) }, 500); }) } async function uploadImages() { //Prepare multiple pictures urls let fileUrl = ['url1', 'url2', 'url3'] for (url of fileUrl) { try { await uploadAImage(url) }catch(err) { (err) //In fact, it is more complicated. If you succeed, you don't need to upload it next time. return ('There is content that failed to upload') } } return 'It all succeeded' } uploadImages().then(res => { (res) }).catch(err => { (err) })
The async and await functions are simply used. I will introduce this one. I will compare the generator later. How it was modified
generator imitates async to execute automatically
Said beforeasync
The function is throughgenerator
Functions are transformed, and an executor is added. Let's try it out to make a simple one.generator
Actuator
Here is a default execution case for async
//Use generator + executor to simply translate async, awaitasync function asyncDefaultFunction() { let res = await new Promise((resolve) => { setTimeout(() => { resolve(1) }, 1000); // setTimeout(() => { // reject('Lalala') // }, 1000); }) return res } asyncDefaultFunction().then(res => { (res) })
We usegenerator
Transformed intoasync
Automatic execution mode
//After using async, we actually wrap the return function and turn yield into awaitfunction asyncFunction() { return co(function* () { //Replace yield and await in the contents of the async function let res = yield new Promise((resolve, reject) => { setTimeout(() => { resolve(1) }, 1000); // setTimeout(() => { // reject('Lalala') // }, 1000); }) return res }) } //Writing a co function that automatically executes generatorfunction co(genFunc) { return new Promise(function (resolve, reject) { const g = genFunc() //Get the generator and prepare to execute function nextFunc(value) { let next; try { next = (value) }catch(err) { return reject(err) } if () { return resolve() } //Why do you need to wrap the promise? The statement may be synchronous functions or asynchronous functions. The truth is guaranteed to be executed correctly //If it is promise, return directly intact. If it is a normal object, wrap it. See promise for details ().then(function(res) { nextFunc(res) }, function(err) { reject(err) }) } nextFunc(undefined) }) } asyncFunction().then(res => { (res) }).catch(err => { (err) })
This makes it easygenerator auto-executor
At the same time, we also discovered,async
Compared with our normal functions, it intensifiesburden
, In order to optimize performance, if we do not use asynchronous functions (orawait
Not available), will be extraasync
Remove it, so that performance waste can be avoided to some extent, especially when it comes to loop statements
at last
After seeing this, I believe we can learn a lot. You can think about whether other languages are similar. When we encounter similar situations, do we suddenly realize that many languages are interconnected, and learning is a process of accumulation. I hope we can gain something this time!
The above is the detailed introduction to the usage of Generator functions and async functions in JS. For more information about JS Generator functions and async functions, please pay attention to my other related articles!