Javascript is known to be single-threaded, and it was originally designed as a GUI programming language for the browser. One of the features of GUI programming is to ensure that the UI thread must not block, otherwise the experience will be poor, or even the interface will freeze.

The so-called single thread is that you can only complete one task at a time, and the task scheduling method is queuing, which is similar to waiting at the bathroom door of the train station. The person in front of you doesn’t complete the task, so you have to stand behind and wait in line.

The advantage of this mode is that it is simple to implement and the execution environment is relatively simple. The disadvantage is that as long as one task takes a long time, subsequent tasks have to wait in line, which will delay the execution of the whole program. Common browser nonresponse (suspended animation) is usually caused by a single piece of Javascript code running for so long (such as an infinite loop) that the entire page gets stuck in one place and no other task can be performed.

To solve this problem, the Javascript language divides the execution mode of the task into two modes: Synchronous and Asynchronous.

“Synchronization” is what is said above, and later tasks wait for the previous task to finish before executing.

What is “asynchronous”?

Asynchronous simply means that a task is divided into two parts, one part is performed first, then the other part is performed, and then the second part is performed when the task is ready.

The following is a flow chart when there are three ABC tasks, synchronous or asynchronous:

synchronous

thread ->|----A-----||-----B-----------||-------C------|
Copy the code

Asynchronous:

A-Start ---------------------------------------- A-End   
           | B-Start ----------------------------------------|--- B-End   
           |   |     C-Start -------------------- C-End      |     |   
           V   V       V                           V         V     V      
  thread-> |-A-|---B---|-C-|-A-|-C-|--A--|-B-|--C--|---A-----|--B--|
Copy the code

Asynchrony is very important. On the browser side, long operations should be performed asynchronously to prevent the browser from becoming unresponsive. The best example of this is Ajax operations. On the server side, “asynchronous mode” is even the only mode, because the execution environment is single-threaded, and if you allow all HTTP requests to be executed synchronously, the server performance deteriorates dramatically and quickly becomes unresponsive.

This paper briefly summarizes the development history of JavaScript asynchronous functions as shown in the figure below:

  1. The callback function
  2. Promise
  3. Generator+co
  4. async,await

The callback function Callbacks

It seems like the place to start is with the callback function.

Asynchronous JavaScript

In Javascript, asynchronous programming can only be done with first-class citizen functions in Javascript: this approach means that we can take one function as an argument to another function, and inside that function we can call the function passed in (the callback function).

This is where callback functions come in: if you pass a function as an argument to another function (called a higher-order function at this point), you can call that function inside the function to do something about it.

A callback function does not return a value (do not try to use a return) and is only used to perform some action inside the function.

Look at the following example:

step1(function (value1) {
    step2(value1, function(value2) {
        step3(value2, function(value3) {
            step4(value3, function(value4) {
                // Do something with value4
            });
        });
    });
});
Copy the code

This is just 4 steps, 4 layers of nested callbacks. What if there are more steps? Obviously this kind of code is just fun to write but has a lot of drawbacks.

The challenges of overusing callbacks:

  • If you don’t organize your code properly, it’s very easy to create callback hell, which makes your code hard to understand.
  • Cannot catch exceptions (try catch is executed synchronously, callbacks are queued and errors cannot be caught)
  • You cannot return a value using a return statement, and you cannot use the throw keyword.

It is for these reasons that the JavaScript world is always looking for feasible solutions that make asynchronous JavaScript development easier. That’s where promises come in, and they solve these problems.

Promise

Promise’s biggest advantage is standardization. All asynchronous libraries are implemented according to a uniform specification, and even async functions can be seamlessly integrated. Therefore, USING Promise to encapsulate API is universal, easy to use and low cost to learn.

A Promise represents the end result of an asynchronous operation.

Promise means [| promised vows] a haven’t finished the operation, but in the future to be completed. The primary way to interact with a Promise is by passing a function into its then method to get the final value of the Promise or the reason for the Promise’s final rejection. There are three main points:

  • Recursively, each asynchronous operation returns a Promise object
  • State machine: Three state transitions that can only be controlled inside the Promise object and cannot be changed externally
  • Global exception handling

1) define

var promise = new Promise(function(resolve, reject) {
  // do a thing, possibly async, then...if (/* everything turned out fine */) {
    resolve("Stuff worked!");
  }
  else {
    reject(Error("It broke")); }});Copy the code

Each Promise definition is the same, passing in an anonymous function in the constructor that takes resolve and reject, which represent success and failure, respectively.

2) call

promise.then(function(text){
    console.log(text)// Stuff worked!
    return Promise.reject(new Error('I did it on purpose.'))
}).catch(function(err){
    console.log(err)
})
Copy the code

Its primary interaction is through the THEN function, and if a Promise successfully executes resolve, it passes the value of resolve to the nearest THEN function as an argument to its THEN function. If something is wrong, reject, then you just pass it up to catch.

We can see some of the principles and features of Propmise by calling the promise example:

Example of a normal call:

let fs = require('fs');
let p = new Promise(function(resolve,reject){
  fs.readFile('./1.txt'.'utf8',(err,data)=>{ err? reject(err):resolve(data); }) }) p.then((data)=>{console.log(data)},(err)=>{console.log(err)});Copy the code

1. A promise instance can call the THEN method multiple times:

p.then((data)=>{console.log(data)},(err)=>{console.log(err)});
p.then((data)=>{console.log(data)},(err)=>{console.log(err)});

Copy the code

2. Promise instances can support chained calls to the THEN method, which jquery implements by returning the current this. But promises cannot be implemented by returning this. That’s because subsequent chain-increments of then don’t depend on the state of the original promise object to determine success or failure.

p.then((data)=>{console.log(data)},(err)=>{console.log(err)}).then((data)=>{console.log(data)})
Copy the code

3. As long as there are successful and failed callbacks in the THEN method, all the returned values (including undefiend) are thrown into the successful callbacks in the next THEN method, and the returned values are passed in as parameters for the successful callbacks in the next THEN method.

The first onethenP.chen ((data)=>{returnUndefined},(err)={console.log()}). Then ((data)=>{console.log(data)}thenP.chen ((data)=>{console.log(1)},(err)={returnUndefined). Then ((data)=>{console.log(data)}Copy the code

4. If either of the successful or failed callbacks in the THEN method are thrown, the failed callback in the next THEN method will be aborted

The first onethenP.chen ((data)=>{throw new Err("Error")},(err)={console.log(1)}).then((data)=>{console.log('success')},(err)=>{console.log(err)}) Output: error firstthenP.chen ((data)=>{console.log(1)},(err)={throw new err ("Error")).then((data)=>{console.log('success')},(err)=>{console.log(err)}) Output: errorCopy the code

5. Success and failure can only go one, if successful, will not go failure, if failure, will not go success;

6. What if the then method returns a promise object instead of a normal value?

Answer: It waits for the execution result of this promise and passes it to the next THEN method. If successful, the result of this promise is passed to the success callback of the next THEN and executed, and if it fails, the error is passed to the failure callback of the next THEN and executed.

7. Have catch to catch errors; If none of catche’s previous THEN methods fail the callback, catche catches the error message and executes it for the bottom of the pocket

P is a failed callback: p.chen ((data)=>{console.log()'success'()}). Then (data) = > {}). Catche (e) {the console. The log ('wrong')}
Copy the code

8. Returns the same results as promise, never succeeding or failing

var  r  = new Promise(function(resolve,reject){
   return r;
})
r.then(function(){
    console.log(1)
},function(err){
    console.log(err)
})
Copy the code

You can see that the result is always pending

When you don’t have an off-the-shelf Promise, you may need to tap into some Promise libraries. A popular option is bluebird. These libraries may offer more functionality than the native solution and are not limited to the features specified by the Promise/A+ standard.

Generator(ECMAScript6)+co

JavaScript generators are a relatively new concept, a new feature of ES6 (also known as ES2015). Imagine the following scenario:

When you’re executing a function, you can pause the function at some point, do some other work, and then return to the function to continue, or even carry on with some new values.

The scenario described above is exactly the kind of problem that JavaScript generator functions aim to solve. When we call a generator function, it does not execute immediately, but requires us to iterate manually (the next method). That is, you call a generator function and it returns you an iterator. The iterator iterates over each breakpoint.

function* foo () {  
  var index = 0;
  while(index < 2) { yield index++; }} var bar = foo(); // Returns an iterator console.log(bar.next()); // { value: 0,done: false }  
console.log(bar.next());    // { value: 1, done: false }  
console.log(bar.next());    // { value: undefined, done: true }  
Copy the code

Further, if you want to make it easier to write asynchronous JavaScript code using generator functions, we can use the co library, which is written by the famous TJ god.

Co is a generator-based flow control tool for Node.js and browsers. With Promise, you can write non-blocking code in a more elegant way.

Using co, we can rewrite the previous example code with the following code:

co(function* (){  
  yield Something.save();
}).then(function() {
  // success
})
.catch(function(err) {
  //error handling
});
Copy the code

You may be asking: How do you implement parallelism? The answer may be simpler than you think, as follows (it’s just promise.all) :

yield [Something.save(), Otherthing.save()];  
Copy the code

The ultimate solution Async/ await

In short, using the async keyword, you can easily do what you used to do with generators and co functions.

Behind the scenes, async functions actually use promises, which is why async functions return a Promise.

Therefore, we use async functions to do something similar to the previous code, which can be rewritten as follows:

async functionsave(Something) { try { await Something.save(); // Wait for the code after await to complete, similar to yield} catch (ex) {// Error handling} console.log('success');
} 
Copy the code

To use async functions, you need to prefix the async keyword at the beginning of the function declaration. After that, you can use the await keyword inside the function, which has the same effect as yield.

Using async functions to complete parallel tasks is very similar to yiled, the only difference is that at this point Promise. All is no longer implicit, you need to call it explicitly:

async function save(Something) {  
    await Promise.all[Something.save(), Otherthing.save()]
}
Copy the code

Async/Await is the ultimate solution for asynchronous operations. Koa 2 was released immediately after Node 7.6 was released, and it is recommended to write Koa middleware using Async functions.

Here is a snippet of code from the Koa 2 application:

exports.list = async (ctx, next) => {
  try {
    let students = await Student.getAllAsync();
  
    await ctx.render('students/index', {
      students : students
    })
  } catch (err) {
    returnctx.api_error(err); }};Copy the code

It does three things

  • Through await Student. GetAllAsync (); To get all the students’ information.
  • Render the page with await ctx.render
  • Exception handling using try/catch because of synchronous code

We’ll also share the basic concepts of Node and eventLoop (macro and micro tasks)

(after)

Reference: The Evolution of Asynchronous JavaScript