Async functions realize asynchronous code in a nearly synchronous way, readable, is very convenient to use.
Its essence is the syntactic sugar of generator function, this article tries to replace async function by writing a function, remove the sugar coating and see.
async
Function of the demo
Let’s start with a demo of async functions:
const getData = () = >
new Promise((resolve) = > setTimeout(() = > resolve('data'), 1000));
async function test() {
const data = await getData();
console.log('data: ', data);
const data2 = await getData();
console.log('data2: ', data2);
return 'success';
}
// Such a function should print data after 1 second, data2 after 1 second, and success
test().then((res) = > console.log(res));
Copy the code
Switch to a demo of generator functions
Each async function executes a generator function, and converting to a generator function is easy:
- To get rid of
async
- add
*
- will
await
Switch toyield
Example translation:
const getData = () = >
new Promise((resolve) = > setTimeout(() = > resolve('data'), 1000));
function* testGen() {
const data = yield getData();
console.log('data: ', data);
const data2 = yield getData();
console.log('data2: ', data2);
return 'success';
}
Copy the code
The basics of generator functions
A generator function is a function, but it returns an iterator.
If you run the entire generator function, the number of calls to Next = yield + 1
Let’s start with a simple demo:
function* gen() {
const data = yield 'first yield';
console.log('data: ', data);
const data2 = yield 'Second yield';
console.log('data2: ', data2);
return 'success';
}
const it = gen();
var { value, done } = it.next('First next');
var { value, done } = it.next('Second next');
var { value, done } = it.next('Third next');
Copy the code
Understanding the next and yield statements is especially critical
next
Let the code inside the function start executing,next
Is itself a function,- itsThe return valueIs an object, number onenThe return value of the next function is the firstna
yield
What follows, or the return value of the generator function,done
Is to indicate whether the function has finished executing. - But the firstna
yield
Is replaced by the firstn+1aThe parameters of the next
- itsThe return valueIs an object, number onenThe return value of the next function is the firstna
next
During execution, encounteredyield
Is suspended
The generator function is expressed in pseudocode as follows:
In the pseudocode, I’m going to replace the yield and the next, and I’m going to put a pause point, so I think it makes sense
function* gen() {
// const data = yield' first yield';
const data = 'Second next'; // The second next argument, which is also the first pause, is not assigned
console.log('data: ', data);
// const data2 = yield' second yield';
const data2 = 'Third next'; // The third next argument, which is also the second pause, is not assigned
console.log('data2: ', data2);
return 'Return value of generator function'; // The generator function is finished
}
// it is iterator
const it = gen();
// The first next function returns the value after the first yield
// var {value, done} = it. Next (' next');
var value = 'first yield',
done = false; // The first next function returns the value after the first yield
// var {value, done} = it. Next (' second next');
var value = 'Second yield',
done = false; // The second next function returns the value after the second yield
// var {value, done} = it. Next (' next');
var value = 'Return value of generator function',
done = true; // Note that done is true, so value is the return value of the generator function
Copy the code
So, when you encounter a yield assignment, be sure to remind yourself that it has nothing to do with what follows the yield!
When you encounter a next assignment, value has nothing to do with the next argument.
Let the generator do it manually
Back to the example of the main generator function:
const getData = () = >
new Promise((resolve) = > setTimeout(() = > resolve('data'), 1000));
function* testGen() {
const data = yield getData(); // data = the second next argument
console.log('data: ', data);
const data2 = yield getData(); // data = the third next argument
console.log('data2: ', data2);
return 'success';
}
Copy the code
If you want data to be data, it doesn’t have to do with yield, it has to do with passing in next.
const it = testGen();
// Value is the first yield and getData() is the promise instance,
let res = it.next();
let promise = res.value;
let done = res.done;
promise.then((data) = > {
// Promise is the second yield followed by getData(), which is actually the promise instance. Note that the next argument here is the assignment to data above
res = it.next(data);
promise = res.value;
done = res.done;
promise.then((data) = > {
// Done is true, and promise is the return value of the generator function. Note that the next argument here is the assignment to data2 above
res = it.next(data);
promise = res.value;
done = res.done;
});
});
Copy the code
Let the generator function execute automatically
Wrapping the above procedure into a function that automatically executes the generator function can take out the repetitions:
function co(gen, ... args) {
return (. args) = > {
constit = gen(... args);// First run
let res = it.next();
let promise = res.value;
let done = res.done;
// Repeat part encapsulation:
const fn = () = > {
if (done) {
return Promise.resolve(promise);
}
promise.then((data) = > {
// Done is true, and promise is the return value of the generator function. Note that the next argument here is the assignment to data2 above
res = it.next(data);
promise = res.value;
done = res.done;
// Keep walking
fn();
});
};
fn();
};
}
co(testGen)();
Copy the code
Better version
It’s a little more complicated, considering the anomalies.
The simplest implementation of handling handwriting async await directly here (20 lines)
function asyncToGenerator(generatorFunc) {
return function () {
const gen = generatorFunc.apply(this.arguments);
return new Promise((resolve, reject) = > {
function step(key, arg) {
let generatorResult;
try {
generatorResult = gen[key](arg);
} catch (error) {
return reject(error);
}
const { value, done } = generatorResult;
if (done) {
return resolve(value);
} else {
return Promise.resolve(value).then(
(val) = > step('next', val),
(err) = > step('throw', err)
);
}
}
step('next');
});
};
}
Copy the code
Ideas:
function asyncToGenerator(generatorFunc) {
// Returns a new function
return function() {
// Call generator to generate iterators
Var gen = testG()
const gen = generatorFunc.apply(this.arguments)
// Return a promise because the outside is using the return value of this function either as.then or await
// var test = asyncToGenerator(testG)
// test().then(res => console.log(res))
return new Promise((resolve, reject) = > {
// Internally define a step function to override the yield barrier step by step
// Key has two values, next and throw, corresponding to gen's next and throw methods respectively
The arg argument is used to yield the promise resolve value to the next yield
function step(key, arg) {
let generatorResult
// This method needs to be wrapped in a try catch
// If an error occurs, discard the promise, reject the error, and catch the error
try {
generatorResult = gen[key](arg)
} catch (error) {
return reject(error)
}
// gen.next() results in a {value, done} structure
const { value, done } = generatorResult
if (done) {
// If this is already done, resolve the promise
// This done will not be true until the last call to next
{done: true, value: 'success'}
// This value is the return value of the generator function
return resolve(value)
} else {
// Call gen.next() every time except at the end
{value: Promise, done: false}
Resolve accepts a Promise as an argument
// Then will only be called when the promise argument is resolved
return Promise.resolve(
// This value corresponds to the promise after yield
value
).then(
// When the value promise is resove, next is executed
// And whenever done is not true, the promise is recursively unwrapped
// Next ().value.then(value => {
// gen.next(value).value.then(value2 => {
// gen.next()
//
// // now done is true and the entire promise is resolved
// // the most external test().then(res => console.log(res)) then starts execution
/ /})
// })
function onResolve(val) {
step("next", val)
},
// If promise is rejected, enter step again
// The difference is that this try catch calls Gen. throw(err).
// Then you catch the promise, reject it
function onReject(err) {
step("throw", err)
},
)
}
}
step("next")}}}Copy the code
reference
- Minimal implementation of Handwriting Async await (20 lines)