This article is published under a Signature 4.0 International (CC BY 4.0) license. Signature 4.0 International (CC BY 4.0)

Node.js asynchronously programming callback

As we know, there are two types of event processing in Node.js: callback and EventEmitter. This article starts with callback.

Error-first callback is the standard for Node.js callbacks.

The first argument is error, and the following arguments are the results.

For example, we go to the interview in real life 🌰. We smile when we succeed in the interview and cry when we fail and try to find the reason for the failure.

try {
    interview(function() {
    	console.log('smile');
    });
} catch(e) {
    console.log('cry', e);
}
function interview(callback) {
    setTimeout((a)= > {
        if (Math.random() < 0.1) {
            callback('success');
        } else {
            throw new Error('fail'); }},500);
}
Copy the code

Instead of catching the error, the try/catch is thrown around the node.js world, causing the program to crash. (This is because each event loop in Node.js is a new Call Stack)

To address these issues, Node.js officially forms the following specification:

interview(function (res) {
    if (res) {
        return console.log('cry');
    }
    console.log('smile');
})
function interview (callback) {
    setTimeout((a)= > {
        if (Math.random() < 0.8) {
            callback(null.'success');
        } else {
            callback(new Error('fail')); }},500);
}
Copy the code

The callback hellCallback hell

XX factory has three rounds of interviews, see the following 🌰

interview(function (err) {
    if (err) {
        return console.log('cry at 1st round');
    }
    interview(function (err) {
        if (err) {
            return console.log('cry at 2nd round');
        }
        interview(function (err) {
            return console.log('cry at 3rd round');
        })
        console.log('smile'); })})function interview (callback) {
    setTimeout((a)= > {
        if (Math.random() < 0.1) {
            callback(null.'success');
        } else {
            callback(new Error('fail')); }},500);
}
Copy the code

Let’s look at how callback behaves in concurrent cases.

Go to two companies at the same time to interview, we will be happy when both interviews are successful, see the following 🌰

var count = 0;
interview(function (err) {
    if (err) {
        return console.log('cry');
    }
    count++;
})
interview(function (err) {
    if (err) {
        return console.log('cry');
    }
    count++;
    if (count) {
        // When count meets certain criteria, the interview is passed
        / /...
        return console.log('smile'); }})function interview (callback) {
    setTimeout((a)= > {
        if (Math.random() < 0.1) {
            callback(null.'success');
        } else {
            callback(new Error('fail')); }},500);
}
Copy the code

With the increase in asynchronous logic comes an increase in nesting depth. The above code has many disadvantages:

  • The code is bloated and difficult to read and maintain
  • The coupling degree is high, and the reconstruction cost is high when the demand changes
  • Because callback functions are anonymous, it is difficult to locate bugs

To solve callback hell, the community has proposed several solutions.

1. Async.js NPM package, which is an asynchronous process control library proposed by the community to solve the callback hell earlier.

2. Thunk programming paradigm, the famous CO module used thunk functions heavily in versions prior to V4. Redux-thunk middleware is also available in Redux.

But they are all gone.

After all, software engineering doesn’t have a silver bullet. The solution to replace them was Promise

Promise

Promise/A+ specification town building, ES6 uses this specification to realize the Promise.

Promise is a solution to asynchronous programming, and ES6 has written it into the language standard, unifying usage, and providing Promise objects natively.

Simply put, a Promise is that the current cycle of events won’t give you a result, but future cycles will.

There was no doubt that Promise was a deceitful and womanizing man.

A Promise is also a state machine that can only change from pending to one of the following states (once changed, it cannot be changed again)

  • This is a pity (called Resolved)
  • rejected
// Nodejs does not print state
// Chrome console does
var promise = new Promise(function(resolve, reject){
    setTimeout((a)= > {
        resolve();
    }, 500)})console.log(promise);
setTimeout((a)= > {
    console.log(promise);
}, 800);
/ / the node. Js
// promise { <pending> }
// promise { <undefined> }
// Put the above code in a closure and throw it into the Google console
/ / in Google
// Promise { <pending> }
// Promise { <resolved>: undefined }
Copy the code

Promise

  • then
  • catch

The Promise in the Resolved state will call back the first. Then

A Promise in the Rejected state calls back to the first. Catch

Any Promise in the Rejected state without a. Catch will cause a global error in the browser/Node environment.

What Promises do better than Callback is solve the problem of asynchronous process control.

(function(){
    var promise = interview();
    promise
        .then((res) = > {
            console.log('smile');
        })
        .catch((err) = > {
            console.log('cry');
        });
    function interview() {
        return new Promise((resoleve ,reject) = > {
            setTimeout((a)= > { 
               if (Math.random() > 0.2) {
                   resolve('success');
               } else {
                   reject(new Error('fail')); }},500);
        });
    }
})();
Copy the code

Executing then and catch returns a new Promise whose final state is determined by the result of executing the callbacks to then and catch. We can look at the following code and print the result:

(function(){
  var promise = interview();
  var promise2 = promise
      .then((res) = > {
          throw new Error('refuse');
      });
      setTimeout((a)= > {
          console.log(promise);
          console.log(promise2);
      }, 800);   
  function interview() {
      return new Promise((resoleve ,reject) = > {
          setTimeout((a)= > { 
             if (Math.random() > 0.2) {
                 resolve('success');
             } else {
                 reject(new Error('fail')); }},500);
      });
  }
})();
// Promise { <resolved>: "success"}
// Promise { <rejected>: Error:refuse }
Copy the code

If the callback function ends up as a throw, the Promise is in the Rejected state.

If the callback function ends up being return, the Promise is resolved.

However, if the callback eventually returns a Promise, that Promise will be in the same state as the callback return Promise.

Promise solves callback hell

Let’s use Promise to re-implement the three rounds of interview code above.

(function() {
    var promise = interview(1)
        .then((a)= > {
            return interview(2);
        })
        .then((a)= > {
            return interview(3);
        })
        .then((a)= > {
            console.log('smile');
        })
        .catch((err) = > {
            console.log('cry at' + err.round + 'round');
        });
    function interview (round) {
        return new Promise((resolve, reject) = > {
            setTimeout((a)= > {
                if (Math.random() > 0.2) {
                    resolve('success');
                } else {
                    var Error = new Error('fail'); error.round = round; reject(error); }},500);
        });
    }
})();
Copy the code

The code implemented by Promise is much more transparent than callback hell.

Promise turns callback hell into somewhat more linear code, removing horizontal extensions and putting callback functions in THEN, but it’s still there in the main flow, at odds with the sequentially linear logic of our brains.

Promise handles concurrency asynchrony

(function() {
    Promise.all([
        interview('Alibaba'),
        interview('Tencent')
    ])
    .then((a)= > {
        console.log('smile');
    })
    .catch((err) = > {
        console.log('cry for' + err.name);
    });
    function interview (name) {
        return new Promise((resolve, reject) = > {
            setTimeout((a)= > {
                if (Math.random() > 0.2) {
                    resolve('success');
                } else {
                    var Error = new Error('fail'); error.name = name; reject(error); }},500);
        });
    }
})();
Copy the code

The catch in the above code is problematic. Note that it can only get the first error.

Generator

Generator and Generator Function are new features introduced in ES6, borrowed from languages such as Python and C#.

The essence of a generator is a special kind of iterator.

function * doSomething() {}
Copy the code

As shown above, functions followed by “*” are Generator.

function * doSomething() {
    interview(1);
    yield; // Line (A)
    interview(2);
}
var person = doSomething();
person.next();  // Execute interview1, first interview, then hover at Line(A)
person.next();  // Resume Line(A) execution, execute interview2, conduct the second secondary interview
Copy the code

Next returns the result

The first person.next() returns {value: “, done:false}

The second person.next() returns {value: “, done:true}

Regarding the result returned by Next, we should know that if done is true, all asynchronous operations in the Generator have completed.

TJ Holowaychuk wrote co, the famous ES6 module, to allow multiple yields to be used in generators. Co source code has a lot of clever implementation, we can read their own.

async/await

The drawback of the Generator is that it has no executor and is itself an iterator designed for computation rather than flow control. The emergence of CO better solves this problem, but why do we have to rely on CO rather than directly achieve it?

Async /await has been chosen as the favoured child.

Async function is a function that exists through an event loop.

Async Function is actually a syntactic sugar wrapper for Promise. It has also been called the ultimate solution to asynchronous programming – write asynchronously in a synchronous manner.

The await keyword can “suspend “async function execution.

The await keyword gets the execution result of a Promise in synchronous writing.

A try/catch can get any error from an await. The catch can only get the first error in a Promise.

Async /await solves callback hell

(async function () {
  try {
      await interview(1);
      await interview(2);
      await interview(3);
  } catch (e) {
      return console.log('cry at' + e.round);
  }
  console.log('smile'); }) ();Copy the code

Async /await processing concurrent asynchrony

(async function () {
    try {
        await Promise.all([interview(1), interview(2)]);
    } catch (e) {
        return console.log('cry at' + e.round);
    }
    console.log('smile'); }) ();Copy the code

Async /await implements asynchronous flow control in just a few lines of code, whether callback or Promise.

Unfortunately async/await didn’t make it into THE ES7 specification (it had to wait until ES8), but it is implemented in Chrome V8 and Node.js V7.6 also integrates async functions.

Summary of practical experience

In common Web applications, it is better to use Promise in the DAO layer and async functions in the Service layer.

Reference:

  • Wolf Book – even more awesome node.js
  • Node.js development practice

❤️ Read three things

1. Please give me a thumbs-up when you see this. Your thumbs-up is the motivation for my creation.

2. Pay attention to the front canteen of the public number, your front canteen, remember to eat on time!

It’s winter, wear more clothes and don’t catch cold ~!