async/await is the executor of the generator. If you want to implement async/await, you must find out what the generator is about.
1. What is the generator function
The generator is a function that generates an iterator, and the yield expression is a flag that suspends execution. You must call the next method of the iterator object to resume the execution of the function and move the pointer to the next state.
Author: GreenTea
Link: https://juejin.cn/post/7021051387312111623
Source: rare earth Nuggets
The copyright belongs to the author. For commercial reprint, please contact the author for authorization, and for non-commercial reprint, please indicate the source.
function *gen() { yield 1; yield 2; return "ending"; } let g = gen(); g.next(); // {value: 1, done: false} g.next(); // {value: 2, done: false} g.next(); // {value: 'ending', done: true} g.next(); // {value: undefined, done: true}
In the above code, first call the gen function to return an iterator, and then call the four next method.
For the first call, the generator function starts to execute until the first yield expression is encountered, and then evaluates the expression after the yield expression and returns it as the value of the value attribute. The value of the done attribute is false, indicating that the traversal has not ended.
In the second call, the generator function takes the input parameter (if any) of the next method as the return value of the yield expression, and then executes from where the last yield expression stopped to the next yield expression, and then evaluates the expression after the yield expression and returns it as the value of the value attribute. The value of the done attribute is false, Indicates that the traversal is not over.
In the third call, the generator function takes the input parameter (if any) of the next method as the return value of the yield expression, and then executes from where the last yield expression stopped until the return statement (if there is no return statement, it will be executed until the end of the function), and then returns the value of the expression after the return statement as the value of the value attribute, The value of the done attribute is true, indicating that the traversal has ended.
The fourth call, when the generator function has finished running, the value attribute of the object returned by the next method is undefined and the value of the done attribute is true. The next method will be called later, and this value will be returned.
Note:
- The expression after the yield expression will be evaluated only when the next method moves the pointer to this sentence;
- The yield expression itself has no return value. The next method can take a parameter, which will be used as the return value of the previous yield expression;
The generator function is actually very similar to the iterator interface. You can use the for...of traversal or expand it into an array using the extension operator. Compared with the iterator interface, in addition to the next method, the generator also has a return method for terminating subsequent processes and a throw method for throwing errors.
2. Simplest actuator
Knowing the above, we can implement the actuator of a generator. For example, there is a generator function:
Author: GreenTea
Link: https://juejin.cn/post/7021051387312111623
Source: rare earth Nuggets
The copyright belongs to the author. For commercial reprint, please contact the author for authorization, and for non-commercial reprint, please indicate the source.
const fullfilled = (param) => { return new Promise(resolve => setTimeout(resolve, 1000, param)) } function *effect() { const a = yield fullfilled("===Test 1==="); console.log(a); const b = yield fullfilled("===Test 2==="); console.log(b); }
Let's take a look at what manual execution should do:
const gen = effect(); // Start executing generator gen.next().value.then(res => { // Take the result of the first Promise as the return value of the yield expression // Print "= = Test 1 === gen.next(res).value.then(res => { // Take the result of the second Promise as the return value of the yield expression // Print "= = Test 2 === gen.next(res); }) })
With this idea, it is easy to implement the actuator:
function asyncToGenerator(fn) { const gen = fn(); let step = (val) => { const { value, done } = gen.next(val); // If the iteration is not over, get the result of the previous step through the then method // Then recursively call step for the next iteration !done && value.then(step); } step(); }
3. Return to Promise
The above code only implements the actuator, which is similar to async/await It's still different. We know async The function takes the inside of the function return Value, wrapped in Promise After returning from, we will realize this function:
function asyncToGenerator(fn) { // Return a Promise instance return new Promise((resolve, reject) => { const gen = fn(); let step = (val) => { const { value, done } = gen.next(val); if (done) { // If the iteration ends, resolve the final result directly // If the function does not return a value, it is undefined resolve(value); } else { // If the iteration is not over, get the result of the previous step through the then method // If Promise succeeds, step is called recursively for the next iteration // If Promise fails, reject directly and do not proceed value.then(step, reject); } } step(); }) }
4. Support non Promise types
Under normal circumstances, await To the right of the command is a Promise Object. If not Promise Object to directly return the corresponding value:
function asyncToGenerator(fn) { return new Promise((resolve, reject) => { const gen = fn(); let step = (val) => { const { value, done } = gen.next(val); if (done) { resolve(value); } else { // In order to save trouble, no judgment is made here // Wrap a Promise directly on a non Promise type Promise.resolve(value).then(step, reject); } } step(); }) }
For more information on website construction and source code transaction, please see GoodMai haomai.com