callback
Functions are first citizens in JavaScript and can be passed into functions as arguments. So we can put code that needs to be executed asynchronously into callback functions and execute it asynchronously.
Example
The delay function is defined as follows, and the type of the callback argument is a function that will be executed after 1 second.
function delay(callback) { console.log('foo'); setTimeout(callback, 1000); } delay(console.log.bind(console, 'bar')); // The console prints foo immediately // 1 second later the console prints barCopy the code
A callback function handling a single asynchronous operation may not seem like a problem, but imagine several scenarios like this.
- Scenario 1: Assume that delayA, delayB, and delayC are all asynchronous operations
callback
Parameters are executed after the operation is complete. Done depends on delayC, delayC depends on delayB, delayB depends on delayA. How do we solve this problem? :
function delayA(callback) { setTimeout(() => { callback(1); }, 1000); } function delayB(fn, somthingFromA) { setTimeout(() => { callback(somthingFromA + 1); }, 2000); } function delayC(fn, somthingFromB) { setTimeout(() => { callback(somthingFromB + 1); }, 3000); } function done(result) { console.log(result); } delayA((feedBackByA) => { delayB((feedBackByB) => { delayC((feedBackByC) => { done(feedBackByC); }, feedBackByB); }, feedBackByA); }); // After 6 seconds, the console outputs 3Copy the code
- Scenario 2: The done operation depends on the result of the delayA, delayB, and delayC operations, but there is no dependency relationship between delayA, delayB, and delayC. They are defined as follows:
function delayA(callback) { setTimeout(() => { callback(1); }, 1000); } function delayB(fn) { setTimeout(() => { callback(2); }, 2000); } function delayC(fn) { setTimeout(() => { callback(3); }, 3000); } function done(somthingFromA, somthingFromB, somthingFromC) {console.log(somthingFromA, somthingFromB, somthingFromB) somthingFromC); }Copy the code
Of course we could use the same solution as scenario 1, but the simultaneous execution of multiple unrelated asynchronous operations delays the execution of the Done operation.
delayA((feedBackByA) => { delayB((feedBackByB) => { delayC((feedBackByC) => { done(feedBackByA, feedBackByB, feedBackByC); }); }); }); After 6 seconds, the console outputs 1, 2, 3Copy the code
So we need to define a helper function that causes multiple asynchronous operations to start executing at the same time and executes the callback at the end of the last asynchronous callback:
/* * FNS: callback: */ var helper = (FNS, callback) => { Const checkList = new Array(fss.length).fill(false); // Save the parameters returned by each asynchronous operation for callback to use const parameters = new Array(fn. Length); Function allCheck() {return checklist. reduce((prev, val) => {return prev && val; }, true); } fns.forEach((fn, index) => { fns((feedBack) => { parameters[index] = feedBack; checkList[index] = true; if (allCheck()) { callback.apply(null, parameters); }}); }); }; helper([delayA, delayB, delayC], done); // Console output 1, 2, 3 after 3 secondsCopy the code
Imagine the requirements of scenario 1 combined with scenario 2, and the code becomes too deeply nested to read. Above all, overuse of callback functions can lead to the legendary callback hell, where code becomes ugly and unmaintainable as the dependencies of asynchronous operations become complex.
Promise
With the introduction of Promises in ES6, which are often used to handle asynchrony, let’s look at how it simplifies the process of dealing with asynchrony.
For those of you who don’t know Promise, take a look at developer.mozilla.org/en-US/docs/…
Let’s start by converting an asynchronous function into a Promise:
Funcion promisify (fn) {return (args) => return new Promise((resolve, reject) => {// fn is an asynchronous operation, the first argument is a callback function, The second argument is the execution argument fn(resolve, args); }); }; }Copy the code
An asynchronous operation that takes callback is as follows:
function delay(callback, content) {
setTimeout(() => callback(content), 1000);
}
Copy the code
Can be promise-made:
const delayPromise = promisify(delay); delayPromise('foo').then((content) => { console.log(content); }); // After 1 second, the console prints fooCopy the code
It doesn’t seem that different from the callback function, but let’s look at scenario 1 above and see what happens to our solution when we use Promise.
Promisify (delayA)(). Then ((feedbackFromA) => {return promisify(delayB)(feedbackFromA); }) .then((feedbackFromB) => { return promisify(delayC)(feedbackFromB); }) .then((feedbackFromC) => { done(feedbackFromC); }); // After 6 seconds, the console outputs 3Copy the code
Now look at scene two:
Promise.all([ promisify(delayA)(), promisify(delayB)(), promisify(delayC)() ]).then(([feedbackFromA, feedbackFromB, feedbackFromC]) => { done(feedbackFromA, feedbackFromB, feedbackFromC); }); // after 3 seconds, the console prints 1,2,3Copy the code
Once all asynchronous operations are encapsulated as Promise objects, the problem of callback hell is clearly eliminated.
Generator
Generator was also introduced in ES6. For those who are not familiar with the Generator please check the documentation developer.mozilla.org/en-US/docs/…
First let’s see how a Generator works:
function* main() { const a = yield 1; Const b = yield 2 + a; // b is the yield 3 + b argument passed in the third call to next; return 4; } const gen = main(); gen.next(); // {value: 1, done: false} gen.next(1); // {value: 3, done: false} gen.next(5); // {value: 8, done: false} gen.next(); // {value: 4, done: true}Copy the code
How does Generator handle asynchrony? Imagine that we have a helper function that causes the generator to complete automatically, so that writing asynchronous code can be just as easy as writing synchronous code.
helper(function* main(args) {
const feedBackFromA = yield promisify(delayA)(args);
const feedBackFromB = yield promisify(delayB)(feedBackFromA);
const feedBackFromC = yield promisify(delayC)(feedBackFromB);
done(feedBackFromC);
})(realArgs);
Copy the code
The helper function should satisfy the following conditions:
- automated
generator
untilgenerator
Is in the completed state yield
After the keywordPromise
The value of resolve is returned as an argument to the next call to next- Return Promise, use
generator
The return value of the instance is the value of resolve
Therefore, define the helper as follows:
function helper(genFn) { return (... args) => new Promise(resolve, reject) => { let gen = genFn(args); function next(prev) => { const { value, done } = gen.next(prev); If (done) {return resolve(Next-value); } else { return value.then(next); } } next(); }); }Copy the code
then
helper(main)(); // The console prints 3 after 6 secondsCopy the code
The problem in scenario 2 can be solved using this helper:
helper(function* main() { const [feedBackFromA, feedBackFromB, feedBackFromC] = yield Promise.all([ promisify(delayA)(); promisify(delayB)(); promisify(delayC)(); ] ); d(feedBackFromA, feedBackFromB, feedBackFromC); }) (); // after 3 seconds, the console prints 1,2,3Copy the code
Interested people can customize the helper to support the following syntax:
helper(function* main() { const [feedBackFromA, feedBackFromB, feedBackFromC] = yield [ promisify(delayA)(); promisify(delayB)(); promisify(delayC)(); ] ; d(feedBackFromA, feedBackFromB, feedBackFromC); }) ();Copy the code
A handy three-party library, CO, provides a powerful helper implementation that accepts Array, Promise, Function, and more after yield.
Async Function
Native support for Async function has been added to ES7.
So the notation is similar to the generator solution above, but only accepts Promise objects after the await keyword. Async Function solution: Scenario 1:
async function main() {
const feedBackFromA = await promisify(delayA)();
const feedBackFromB = await promisify(delayB)(feedBackFromA);
const feedBackFromC = await promisify(delayC)(feedBackFromB);
d(feedBackFromC);
}
Copy the code
Scene 2:
Async function main() {const feedBacks = await promise. all([promisify(delayA)(/* a parameter */); promisify(delayB)(/* b parameter) */); promisify(delayC)(/* c parameter */);] ); d(feedBacks); }Copy the code
Error handling
callback
Handle errors in their respective callbacks.
a(() => {
if (err) {
// handle the error of a
}
b(() => {
if (err) {
// handle the error of b
}
...
});
});
Copy the code
Promise
Use the catch method to capture errors uniformly throughout the Promise process, or separately in the second argument to the then method.
promisify(delayA)()
.then(promisify(delayB), (err) => {
// handle the error of a
})
.catch((err) => {
// handle all errors
});
Copy the code
The Generator and Async Function
Writing is close to synchronization, so you can use try catch directly to handle errors.
Async function main() {try {const feedBackFromA = await promisify(delayA)(/* a argument */); const feedBackFromB = await promisify(delayB)(aRes); const feedBackFromC = await promisify(delayC)(feedBackFromB); d(feedBackFromC); } catch (err) { // handle all errors } }Copy the code
Creative Commons Attribution – Non-commercial Use – Same way Share 4.0 International License