Node.js Asynchronous Programming Evolution Theory

Keywords: node.js Programming Google npm Python

This article uses the "Signature 4.0 International (CC BY 4.0)" license agreement, and welcomes reprinting, or re-modifying the use, provided the source is indicated. Signature 4.0 International (CC BY 4.0)

Node.js asynchronous programming callback

We know that there are two ways to handle events in Node.js, callback and EventEmitter.This article first introduces callback.

error-first callback Error priority is Node.js Criteria for callback modes.

The first parameter iserror,The latter parameter is the result.

Let's take real-life interviews as an example🌰,The interview succeeded. We spilled a smile. When the interview failed, we cried and tried to find out why it failed.

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

As the code above runs, try/catch doesn't catch the error as we thought. Instead, it throws the error into the Node.js global, causing the program to crash.(Because every event loop of Node.js is a new call stack Call Stack)

To address these issues, Node.js officially formed the following specifications:

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

Callback hell

XX factory has three rounds of interviews, see below_

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(() => {
        if (Math.random() < 0.1) {
            callback(null, 'success');
        } else {
            callback(new Error('fail'));
        }
    }, 500);
}

Let's see how callback behaves in concurrent situations.

Go to both companies for interviews at the same time. We will be happy when both interviews are successful. Look at this

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, all interviews pass
        //...
        return console.log('smile');
    }
})
function interview (callback) {
    setTimeout(() => {
        if (Math.random() < 0.1) {
            callback(null, 'success');
        } else {
            callback(new Error('fail'));
        }
    }, 500);
}

The increase in asynchronous logic is accompanied by an increase in the depth of nesting.The code above has many drawbacks:

  • Bloated code, not good for reading and maintenance
  • High coupling, high refactoring cost when demand changes
  • It is difficult to locate bug s because callback functions are anonymous

To address callback hell, the community has come up with some solutions.

1.async.js The npm package is an asynchronous process control repository for resolving callback hell proposed by the community at an early stage.

2.thunk programming paradigm, the well-known co module used Thunk functions extensively in pre-v4 versions.Redux also has the middleware redux-thunk.

But they all retired from the stage of history.

After all, there is no silver bullet in software engineering, and the alternative to them is Promise

Promise

Promise/A+ Specification townships, ES6 uses this specification to implement Promise.

Promise is a solution for asynchronous programming, which ES6 writes into language standards, unifies usage, and natively provides Promise objects.

Simply put, Promise means that the current event loop will not get results, but future event loops will give you results.

There is no doubt that Promise is a slag man.

Promise is also a state machine and can only change from pending to the following (once changed it cannot be changed anymore)

  • Fulfilled (referred to as resolved in this article)
  • rejected
// nodejs will not print status
// In the Chrome console, you can
var promise = new Promise(function(resolve, reject){
    setTimeout(() => {
        resolve();
    }, 500)
}) 
console.log(promise);
setTimeout(() => {
    console.log(promise);
}, 800);
// In node.js
// promise { <pending> }
// promise { <undefined> }
// Put the above code in the closure and throw it in the google console
// In google
// Promise { <pending> }
// Promise { <resolved>: undefined }

Promise

  • then
  • catch

Promise in resolved state calls back the first one after.

Promise in rejected state calls back the first one after.

Any rejected state with No. catch behind Proise will result in a global error in the browser/node environment.

What makes Promise better than callback is that it solves asynchronous process control problems.

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

Executing the then and catch returns a new Promise whose final state is based on then
And catch's callback function.We can see the following code and the printed results:

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

If the callback function is throw eventually, the Promise is rejected.

If the callback function is ultimately return, the Promise is resolved.

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

Promise Resolves Callback Hell

Let's re-implement the three-round interview code above with Promise.

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

The code implemented by Promise is much more transparent than calling back to Hell.

Promise somewhat transforms Callback Hell into a more linear code, removing lateral extensions and putting callback functions in the then, but it still exists in the main process, which is different from our brain's sequential linear thinking logic.

Promise handles concurrent asynchronous

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

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

Generator

Generator and Generator Function are new features introduced in ES6 and are used for reference in Python, C#and other languages.

Generators are special iterators in nature.

function * doSomething() {}

As shown above, the Generator is followed by a'*'function.

function * doSomething() {
    interview(1);
    yield; // Line (A)
    interview(2);
}
var person = doSomething();
person.next();  // Execute interview 1, first interview, and hover over Line(A)
person.next();  // Resume Line(A) point execution, interview 2, second interview

Return of next

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

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

With respect to the return of next, we need to know that if the value of Don is true, it means that the asynchronous operation in Generator has been completely executed.

In order to be able to use multiple yield s in Generator, TJ Holowaychuk wrote the well-known ES6 module co. Source code for co There are many clever implementations that you can read on your own.

async/await

The disadvantage of Generator is that there is no executor, it is an iterator designed for calculation, not for process control.The advent of cobalt solves this problem better, but why do we have to rely on cobalt instead of directly implementing it?

async/await was chosen as the proud child of heaven.

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

The async function is actually Promise's grammatical sugar packaging.It is also known as the ultimate solution for asynchronous programming - writing asynchronous in a synchronous manner.

The await keyword "pauses" the execution of the async function.

The await keyword can get Promise execution results in synchronous writing.

try/catch can get any error from await, which solves the problem that catch in Promise above can only get the first error.

async/await Resolve 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');
})();

async/await processing concurrent asynchronous

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

Whether compared to callback or Promise, async/await achieves asynchronous process control in just a few lines of code.

Unfortunately, async/await did not eventually enter the ES7 specification (only until ES8), but it was implemented in the Chrome V8 engine and the async function was integrated with Node.js v7.6.

Summary of Practice Experience

In common Web applications, Promise is better at the DAO layer and async is better at the Service layer.

Reference resources:

  • Wolf-book - even greater Node.js
  • Node.js Development Actual

Having seen three things

1. When I see this, I'd like to give you some approval. Your approval is the driving force of my creation.

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

3. In winter, don't cool down by wearing more clothes!

Posted by isedeasy on Tue, 19 Nov 2019 20:15:42 -0800