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 operationscallbackParameters 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:

  • automatedgeneratoruntilgeneratorIs in the completed state
  • yieldAfter the keywordPromiseThe value of resolve is returned as an argument to the next call to next
  • Return Promise, usegeneratorThe 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